Compare commits

..

No commits in common. "master" and "feat/add-search-command" have entirely different histories.

107 changed files with 2664 additions and 6226 deletions

3
.gitignore vendored

@ -8,6 +8,3 @@
.gigaide .gigaide
*.out *.out
e2e-tests/alr
commit_msg.txt

@ -47,4 +47,4 @@ issues:
# TODO: remove # TODO: remove
- linters: - linters:
- staticcheck - staticcheck
text: "SA1019: interp.ExecHandler" text: "SA1019:"

@ -21,7 +21,7 @@ build: check-no-root $(BIN)
export CGO_ENABLED := 0 export CGO_ENABLED := 0
$(BIN): $(BIN):
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@ go build -ldflags="-X 'gitea.plemya-x.ru/xpamych/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
check-no-root: check-no-root:
@if [[ "$$(whoami)" == 'root' ]]; then \ @if [[ "$$(whoami)" == 'root' ]]; then \
@ -37,7 +37,6 @@ install: \
$(INSTALED_BIN): $(BIN) $(INSTALED_BIN): $(BIN)
install -Dm755 $< $@ install -Dm755 $< $@
setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN)
$(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION) $(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION)
install -Dm755 $< $@ install -Dm755 $< $@
@ -67,16 +66,7 @@ i18n:
$(XGOTEXT_BIN) --output ./internal/translations/default.pot $(XGOTEXT_BIN) --output ./internal/translations/default.pot
msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot
msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot
bash scripts/i18n-badge.sh
test-coverage: test-coverage:
go test ./... -v -coverpkg=./... -coverprofile=coverage.out go test ./... -v -coverpkg=./... -coverprofile=coverage.out
bash scripts/coverage-badge.sh bash scripts/coverage-badge.sh
update-deps-cve:
bash scripts/update-deps-cve.sh
e2e-test: clean build
rm -f ./e2e-tests/alr
cp alr e2e-tests
go test -tags=e2e ./...

@ -3,13 +3,13 @@
</p> </p>
<b></b> <b></b>
[![Go Report Card](https://goreportcard.com/badge/gitea.plemya-x.ru/Plemya-x/ALR)](https://goreportcard.com/report/gitea.plemya-x.ru/Plemya-x/ALR) ![Test coverage](./assets/coverage-badge.svg) ![ru translate](./assets/i18n-ru-badge.svg) [![Go Report Card](https://goreportcard.com/badge/gitea.plemya-x.ru/Plemya-x/ALR)](https://goreportcard.com/report/gitea.plemya-x.ru/Plemya-x/ALR) ![Test coverage](./coverage-badge.svg)
# ALR (Any Linux Repository) # ALR (Any Linux Repository)
ALR - это независимая от дистрибутива система сборки для Linux (форк [LURE](https://github.com/lure-sh/lure), аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. ALR готов к общему использованию, но все еще может время от времени ломаться или изменяться. ALR - это независимая от дистрибутива система сборки для Linux, аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. alr готов к общему использованию, но все еще может время от времени ломаться или заменяться.
ALR написан на чистом Go и после сборки не имеет зависимостей. Для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `apt-get` `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе используется поддерживаемый менеджер пакетов, то он будет обнаружен и использован автоматически. ALR написан на чистом Go и после сборки не имеет зависимостей. Единственное, для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе существует поддерживаемый менеджер пакетов, он будет обнаружен и использован автоматически.
--- ---
@ -23,14 +23,14 @@ ALR написан на чистом Go и после сборки не имее
curl -fsSL plemya-x.ru/alr/install.sh | bash curl -fsSL plemya-x.ru/alr/install.sh | bash
``` ```
**ВАЖНО**: При этом скрипт будет загружен и запущен с <https://plemya-x.ru/alr/install.sh>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. **ВАЖНО**: При этом скрипт будет загружен и запущен с <https://gitea.plemya-x.ru/Plemya-x/ALR/src/branch/master/scripts/install.sh>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их.
### Сборка из исходного кода ### Сборка из исходного кода
Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите: Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите:
```shell ```shell
make build -B make build
sudo make install sudo make install
``` ```
@ -44,7 +44,7 @@ ALR был создан потому, что упаковка программн
## Документация ## Документация
Документация находится в [Wiki](https://disc.plemya-x.ru/c/alr/wiki-alr). Документация по всем этим вопросам находится в [Wiki](https://gitea.plemya-x.ru/xpamych/ALR/wiki/Home).
--- ---
@ -52,15 +52,17 @@ ALR был создан потому, что упаковка программн
Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD. Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.
Например, репозиторий [Plemya-x/alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так: Например, репозиторий [Plemya-x/xpamych-alr-repo](https://gitea.plemya-x.ru/Plemya-x/xpamych-alr-repo.git) можно подключить так:
``` ```
alr addrepo --name alr-repo --url https://gitea.plemya-x.ru/Plemya-x/alr-repo.git alr addrepo --name xpamych-alr-repo --url https://gitea.plemya-x.ru/Plemya-x/xpamych-alr-repo.git
``` ```
--- ---
## Соцсети ## Соцсети
VK - https://vk.com/plemya_kh VK - https://vk.com/plemya_kh
Discord - https://discord.com/channels/817759634105827358/1261631565084233749
Telegram - https://t.me/plemyakh Telegram - https://t.me/plemyakh
## Спасибы ## Спасибы
@ -73,6 +75,3 @@ Telegram - https://t.me/plemyakh
- <https://github.com/goreleaser/nfpm> - <https://github.com/goreleaser/nfpm>
- <https://github.com/charmbracelet/bubbletea> - <https://github.com/charmbracelet/bubbletea>
- <https://gitlab.com/cznic/sqlite> - <https://gitlab.com/cznic/sqlite>
Благодарим за активное участие в развитии проекта:
- Maks1mS <maxim@slipenko.com>

@ -1,18 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="129" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<mask id="round">
<rect width="129" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)">
<rect width="75" height="20" fill="#555"/>
<rect x="75" width="64" height="20" fill="#4c1"/>
<rect width="129" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text>
<text x="37" y="14">ru translate</text>
<text x="100" y="15" fill="#010101" fill-opacity=".3">100.00%</text>
<text x="100" y="14">100.00%</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 942 B

182
build.go

@ -28,12 +28,14 @@ import (
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func BuildCmd() *cli.Command { func BuildCmd() *cli.Command {
@ -48,9 +50,9 @@ func BuildCmd() *cli.Command {
Usage: gotext.Get("Path to the build script"), Usage: gotext.Get("Path to the build script"),
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "subpackage", Name: "script-package",
Aliases: []string{"sb"}, Aliases: []string{"sp"},
Usage: gotext.Get("Specify subpackage in script (for multi package script only)"), Usage: gotext.Get("Specify package in script (for multi package script only)"),
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "package", Name: "package",
@ -64,67 +66,27 @@ func BuildCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil {
return err
}
wd, err := os.Getwd()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
}
wd, wdCleanup, err := Mount(wd)
if err != nil {
return err
}
defer wdCleanup()
ctx := c.Context ctx := c.Context
cfg := config.New()
deps, err := appbuilder. db := database.New(cfg)
New(ctx). rs := repos.New(cfg, db)
WithConfig(). err := db.Init(ctx)
WithDB().
WithReposNoPull().
WithDistroInfo().
WithManager().
Build()
if err != nil { if err != nil {
return cli.Exit(err, 1) slog.Error(gotext.Get("Error db init"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
var script string var script string
var packages []string var packages []string
var res *build.BuildResult // Проверяем, установлен ли флаг script (-s)
var scriptArgs *build.BuildPackageFromScriptArgs repoDir := cfg.GetPaths(ctx).RepoDir
var dbArgs *build.BuildPackageFromDbArgs
buildArgs := &build.BuildArgs{
Opts: &types.BuildOpts{
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
},
PkgFormat_: build.GetPkgFormat(deps.Manager),
Info: deps.Info,
}
switch { switch {
case c.IsSet("script"): case c.IsSet("script"):
script, err = filepath.Abs(c.String("script")) script = c.String("script")
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Cannot get absolute script path"), err)
}
packages = append(packages, c.String("script-package")) packages = append(packages, c.String("script-package"))
scriptArgs = &build.BuildPackageFromScriptArgs{
Script: script,
Packages: packages,
BuildArgs: *buildArgs,
}
case c.IsSet("package"): case c.IsSet("package"):
// TODO: handle multiple packages // TODO: handle multiple packages
packageInput := c.String("package") packageInput := c.String("package")
@ -137,97 +99,83 @@ func BuildCmd() *cli.Command {
packageSearch = arr[0] packageSearch = arr[0]
} }
pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageSearch}) pkgs, _, _ := rs.FindPkgs(ctx, []string{packageSearch})
if err != nil { pkg, ok := pkgs[packageSearch]
return cliutils.FormatCliExit("failed to find pkgs", err) if len(pkg) < 1 || !ok {
} slog.Error(gotext.Get("Package not found"))
os.Exit(1)
pkg := cliutils.FlattenPkgs(ctx, pkgs, "build", c.Bool("interactive"))
if len(pkg) < 1 {
return cliutils.FormatCliExit(gotext.Get("Package not found"), nil)
} }
if pkg[0].BasePkgName != "" { if pkg[0].BasePkgName != "" {
script = filepath.Join(repoDir, pkg[0].Repository, pkg[0].BasePkgName, "alr.sh")
packages = append(packages, pkg[0].Name) packages = append(packages, pkg[0].Name)
} } else {
script = filepath.Join(repoDir, pkg[0].Repository, pkg[0].Name, "alr.sh")
dbArgs = &build.BuildPackageFromDbArgs{
Package: &pkg[0],
Packages: packages,
BuildArgs: *buildArgs,
} }
default: default:
return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil) script = filepath.Join(repoDir, "alr.sh")
} }
if scriptArgs != nil { // Проверка автоматического пулла репозиториев
scriptFile := filepath.Base(scriptArgs.Script) if cfg.AutoPull(ctx) {
newScriptDir, scriptDirCleanup, err := Mount(filepath.Dir(scriptArgs.Script)) err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1)
} }
defer scriptDirCleanup()
scriptArgs.Script = filepath.Join(newScriptDir, scriptFile)
} }
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { // Обнаружение менеджера пакетов
return err mgr := manager.Detect()
if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
} }
installer, installerClose, err := build.GetSafeInstaller() info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error parsing os release"), "err", err)
} os.Exit(1)
defer installerClose()
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
return err
} }
scripter, scripterClose, err := build.GetSafeScriptExecutor() builder := build.NewBuilder(
if err != nil {
return err
}
defer scripterClose()
builder, err := build.NewMainBuilder(
deps.Cfg,
deps.Manager,
deps.Repos,
scripter,
installer,
)
if err != nil {
return err
}
if scriptArgs != nil {
res, err = builder.BuildPackageFromScript(
ctx, ctx,
scriptArgs, types.BuildOpts{
Packages: packages,
Script: script,
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
},
rs,
info,
cfg,
) )
} else if dbArgs != nil {
res, err = builder.BuildPackageFromDb(
ctx,
dbArgs,
)
}
// Сборка пакета
pkgPaths, _, err := builder.BuildPackage(ctx)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error building package"), err) slog.Error(gotext.Get("Error building package"), "err", err)
os.Exit(1)
} }
for _, pkgPath := range res.PackagePaths { // Получение текущей рабочей директории
wd, err := os.Getwd()
if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
}
// Перемещение собранных пакетов в рабочую директорию
for _, pkgPath := range pkgPaths {
name := filepath.Base(pkgPath) name := filepath.Base(pkgPath)
err = osutils.Move(pkgPath, filepath.Join(wd, name)) err = osutils.Move(pkgPath, filepath.Join(wd, name))
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err) slog.Error(gotext.Get("Error moving the package"), "err", err)
os.Exit(1)
} }
} }
slog.Info(gotext.Get("Done"))
return nil return nil
}, },
} }

@ -11,7 +11,7 @@
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">coverage</text> <text x="33.5" y="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">16.4%</text> <text x="86" y="15" fill="#010101" fill-opacity=".3">20.5%</text>
<text x="86" y="14">16.4%</text> <text x="86" y="14">20.5%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 926 B

@ -1,72 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
//go:build e2e
package e2etests_test
import (
"bytes"
"testing"
"github.com/efficientgo/e2e"
"github.com/stretchr/testify/assert"
)
func TestE2EAlrAddRepo(t *testing.T) {
dockerMultipleRun(
t,
"add-repo-remove-repo",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"bash",
"-c",
"cat /etc/alr/alr.toml",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sudo",
"alr",
"removerepo",
"--name",
"alr-repo",
))
assert.NoError(t, err)
var buf bytes.Buffer
err = r.Exec(e2e.NewCommand(
"bash",
"-c",
"cat /etc/alr/alr.toml",
), e2e.WithExecOptionStdout(&buf))
assert.NoError(t, err)
assert.Contains(t, buf.String(), "rootCmd")
},
)
}

@ -1,40 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
func TestE2EBashCompletion(t *testing.T) {
dockerMultipleRun(
t,
"bash-completion",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"alr", "install", "--generate-bash-completion",
))
assert.NoError(t, err)
},
)
}

@ -1,190 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
//go:build e2e
package e2etests_test
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"log"
"os"
"os/exec"
"testing"
"time"
"github.com/efficientgo/e2e"
"github.com/stretchr/testify/assert"
expect "github.com/tailscale/goexpect"
)
// DebugWriter оборачивает io.Writer и логирует все записываемые данные.
type DebugWriter struct {
prefix string
writer io.Writer
}
func (d *DebugWriter) Write(p []byte) (n int, err error) {
log.Printf("%s: Writing data: %q", d.prefix, p) // Логируем данные
return d.writer.Write(p)
}
// DebugReader оборачивает io.Reader и логирует все читаемые данные.
type DebugReader struct {
prefix string
reader io.Reader
}
func (d *DebugReader) Read(p []byte) (n int, err error) {
n, err = d.reader.Read(p)
if n > 0 {
log.Printf("%s: Read data: %q", d.prefix, p[:n]) // Логируем данные
}
return n, err
}
func e2eSpawn(runnable e2e.Runnable, command e2e.Command, timeout time.Duration, opts ...expect.Option) (expect.Expecter, <-chan error, error, *io.PipeWriter) {
resCh := make(chan error)
// Создаем pipe для stdin и stdout
stdinReader, stdinWriter := io.Pipe()
stdoutReader, stdoutWriter := io.Pipe()
debugStdinReader := &DebugReader{prefix: "STDIN", reader: stdinReader}
debugStdoutWriter := &DebugWriter{prefix: "STDOUT", writer: stdoutWriter}
go func() {
err := runnable.Exec(
command,
e2e.WithExecOptionStdout(debugStdoutWriter),
e2e.WithExecOptionStdin(debugStdinReader),
e2e.WithExecOptionStderr(debugStdoutWriter),
)
resCh <- err
}()
exp, chnErr, err := expect.SpawnGeneric(&expect.GenOptions{
In: stdinWriter,
Out: stdoutReader,
Wait: func() error {
return <-resCh
},
Close: func() error {
stdinWriter.Close()
stdoutReader.Close()
return nil
},
Check: func() bool { return true },
}, timeout, expect.Verbose(true), expect.VerboseWriter(os.Stdout))
return exp, chnErr, err, stdinWriter
}
var ALL_SYSTEMS []string = []string{
"ubuntu-24.04",
"alt-sisyphus",
"fedora-41",
// "archlinux",
// "alpine",
// "opensuse-leap",
// "redos-8",
}
var AUTOREQ_AUTOPROV_SYSTEMS []string = []string{
// "alt-sisyphus",
"fedora-41",
}
var RPM_SYSTEMS []string = []string{
"fedora-41",
}
var COMMON_SYSTEMS []string = []string{
"ubuntu-24.04",
}
func init() {
for _, id := range ALL_SYSTEMS {
buildAlrTestImage(id)
}
}
func buildAlrTestImage(id string) {
cmd := exec.Command(
"docker",
"build",
"-t", fmt.Sprintf("alr-testimage-%s", id),
"-f", fmt.Sprintf("images/Dockerfile.%s", id),
".",
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
fmt.Println("Error:", err)
return
}
}
func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testing.T, runnable e2e.Runnable)) {
t.Run(name, func(t *testing.T) {
for _, id := range ids {
t.Run(id, func(t *testing.T) {
t.Parallel()
dockerName := fmt.Sprintf("alr-test-%s-%s", name, id)
hash := sha256.New()
hash.Write([]byte(dockerName))
hashSum := hash.Sum(nil)
hashString := hex.EncodeToString(hashSum)
truncatedHash := hashString[:8]
e, err := e2e.New(e2e.WithVerbose(), e2e.WithName(fmt.Sprintf("alr-%s", truncatedHash)))
assert.NoError(t, err)
t.Cleanup(e.Close)
imageId := fmt.Sprintf("alr-testimage-%s", id)
runnable := e.Runnable(dockerName).Init(
e2e.StartOptions{
Image: imageId,
Volumes: []string{
// "./alr:/usr/bin/alr",
},
Privileged: true,
},
)
assert.NoError(t, e2e.StartAndWaitReady(runnable))
f(t, runnable)
})
}
})
}
func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expects []expect.Batcher) {
exp, _, err, _ := e2eSpawn(
r,
e2e.NewCommand("/bin/bash"), 25*time.Second,
expect.Verbose(true),
)
assert.NoError(t, err)
_, err = exp.ExpectBatch(
expects,
timeout,
)
assert.NoError(t, err)
}

@ -1,43 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"time"
"github.com/efficientgo/e2e"
expect "github.com/tailscale/goexpect"
)
func TestE2EAlrFix(t *testing.T) {
dockerMultipleRun(
t,
"run-fix",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
runTestCommands(t, r, time.Second*30, []expect.Batcher{
&expect.BSnd{S: "alr fix\n"},
&expect.BExp{R: `--> Done`},
&expect.BSnd{S: "echo $?\n"},
&expect.BExp{R: `^0\n$`},
})
},
)
}

@ -1,56 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
func TestE2EGroupAndSummaryField(t *testing.T) {
dockerMultipleRun(
t,
"group-and-summary-field",
RPM_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group}}\" | grep ^System/Base$",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary}}\" | grep \"^Custom summary$\"",
))
assert.NoError(t, err)
},
)
}

@ -1,4 +0,0 @@
FROM alpine:latest
RUN adduser -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

@ -1,6 +0,0 @@
FROM registry.altlinux.org/sisyphus/alt:latest
RUN apt-get update && apt-get install -y ca-certificates rpm-build
RUN useradd -m -s /bin/bash alr-user
USER alr-user
WORKDIR /home/alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

@ -1,4 +0,0 @@
FROM archlinux:latest
RUN useradd -m -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

@ -1,18 +0,0 @@
FROM fedora:41
RUN dnf install -y ca-certificates sudo rpm-build bindfs
RUN <<EOF
useradd -m -s /bin/bash -G wheel user
echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/user
chmod 0440 /etc/sudoers.d/user
useradd -m -s /bin/bash alr
mkdir -p /var/cache/alr /etc/alr
chown alr:alr /var/cache/alr /etc/alr
EOF
COPY ./alr /usr/bin
RUN <<EOF
setcap cap_setuid,cap_setgid+ep /usr/bin/alr
EOF
USER user
WORKDIR /home/user
ENTRYPOINT ["tail", "-f", "/dev/null"]

@ -1,4 +0,0 @@
FROM opensuse/leap:latest
RUN useradd -m -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

@ -1,4 +0,0 @@
FROM registry.red-soft.ru/ubi8/ubi:latest
RUN useradd -m -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

@ -1,17 +0,0 @@
FROM ubuntu:24.10
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates sudo libcap2-bin
RUN <<EOF
useradd -m -s /bin/bash user
echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/user
chmod 0440 /etc/sudoers.d/user
useradd -m -s /bin/bash alr
mkdir -p /var/cache/alr /etc/alr
chown alr:alr /var/cache/alr /etc/alr
EOF
COPY ./alr /usr/bin
RUN <<EOF
setcap cap_setuid,cap_setgid+ep /usr/bin/alr
EOF
USER user
ENTRYPOINT ["tail", "-f", "/dev/null"]

@ -1,51 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
func TestE2EIssue32Interactive(t *testing.T) {
dockerMultipleRun(
t,
"issue-32-interactive",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
assert.NoError(t, r.Exec(e2e.NewCommand(
"sudo", "alr", "--interactive=false", "remove", "ca-certificates",
)))
assert.NoError(t, r.Exec(e2e.NewCommand(
"sudo", "alr", "--interactive=false", "remove", "openssl",
)))
assert.NoError(t, r.Exec(e2e.NewCommand(
"alr", "fix",
)))
assert.NoError(t, r.Exec(e2e.NewCommand(
"sudo", "alr", "--interactive=false", "install", "ca-certificates",
)))
},
)
}

@ -1,81 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
func TestE2EIssue41AutoreqSkiplist(t *testing.T) {
dockerMultipleRun(
t,
"issue-41-autoreq-skiplist",
AUTOREQ_AUTOPROV_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"alr",
"ref",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"alr",
"build",
"-p",
"alr-repo/test-autoreq-autoprov",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sh",
"-c",
"rpm -qp --requires *.rpm | grep \"^/bin/sh$\"",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sh",
"-c",
"rpm -qp --requires *.rpm | grep \"^/bin/bash$\"",
))
assert.Error(t, err)
err = r.Exec(e2e.NewCommand(
"sh",
"-c",
"rpm -qp --requires *.rpm | grep \"^/bin/zsh$\"",
))
assert.Error(t, err)
},
)
}

@ -1,56 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
func TestE2EIssue50InstallMultiple(t *testing.T) {
dockerMultipleRun(
t,
"issue-50-install-multiple",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sudo", "alr", "in", "foo-pkg", "bar-pkg",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("cat", "/opt/foo"))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("cat", "/opt/bar"))
assert.NoError(t, err)
},
)
}

@ -1,53 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
func TestE2EIssue53LcAllCInfo(t *testing.T) {
dockerMultipleRun(
t,
"issue-53-lc-all-c-info",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"bash",
"-c",
"LANG=C alr info alr-bin",
))
assert.NoError(t, err)
},
)
}

@ -1,58 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
func TestE2EIssue59RmCompletion(t *testing.T) {
dockerMultipleRun(
t,
"issue-59-rm-completion",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sudo", "alr", "in", "foo-pkg", "bar-pkg",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^foo-pkg$"))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^bar-pkg$"))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^test-autoreq-autoprov$"))
assert.Error(t, err)
},
)
}

@ -1,44 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"time"
"github.com/efficientgo/e2e"
expect "github.com/tailscale/goexpect"
)
func TestE2EAlrVersion(t *testing.T) {
dockerMultipleRun(
t,
"check-version",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
runTestCommands(t, r, time.Second*10, []expect.Batcher{
&expect.BSnd{S: "alr version\n"},
&expect.BExp{R: `^v\d+\.\d+\.\d+(?:-\d+-g[a-f0-9]+)?\n$`},
&expect.BSnd{S: "echo $?\n"},
&expect.BExp{R: `^0\n$`},
})
},
)
}

64
fix.go

@ -22,14 +22,13 @@ package main
import ( import (
"log/slog" "log/slog"
"os" "os"
"path/filepath"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func FixCmd() *cli.Command { func FixCmd() *cli.Command {
@ -37,63 +36,32 @@ func FixCmd() *cli.Command {
Name: "fix", Name: "fix",
Usage: gotext.Get("Attempt to fix problems with ALR"), Usage: gotext.Get("Attempt to fix problems with ALR"),
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
return err
}
ctx := c.Context ctx := c.Context
deps, err := appbuilder. db.Close()
New(ctx). paths := config.GetPaths(ctx)
WithConfig().
Build() slog.Info(gotext.Get("Removing cache directory"))
err := os.RemoveAll(paths.CacheDir)
if err != nil { if err != nil {
return cli.Exit(err, 1) slog.Error(gotext.Get("Unable to remove cache directory"), "err", err)
} os.Exit(1)
defer deps.Defer()
cfg := deps.Cfg
paths := cfg.GetPaths()
slog.Info(gotext.Get("Clearing cache directory"))
// Remove all nested directories of paths.CacheDir
dir, err := os.Open(paths.CacheDir)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err)
}
defer dir.Close()
entries, err := dir.Readdirnames(-1)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err)
}
for _, entry := range entries {
err = os.RemoveAll(filepath.Join(paths.CacheDir, entry))
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err)
}
} }
slog.Info(gotext.Get("Rebuilding cache")) slog.Info(gotext.Get("Rebuilding cache"))
err = os.MkdirAll(paths.CacheDir, 0o755) err = os.MkdirAll(paths.CacheDir, 0o755)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err) slog.Error(gotext.Get("Unable to create new cache directory"), "err", err)
os.Exit(1)
} }
deps, err = appbuilder. err = repos.Pull(ctx, config.Config(ctx).Repos)
New(ctx).
WithConfig().
WithDB().
WithReposForcePull().
Build()
if err != nil { if err != nil {
return cli.Exit(err, 1) slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1)
} }
defer deps.Defer()
slog.Info(gotext.Get("Done")) slog.Info(gotext.Get("Done"))

52
go.mod

@ -1,27 +1,22 @@
module gitea.plemya-x.ru/Plemya-x/ALR module gitea.plemya-x.ru/Plemya-x/ALR
go 1.23.0 go 1.22
toolchain go1.24.2 toolchain go1.23.5
require ( require (
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PuerkitoBio/purell v1.2.0 github.com/PuerkitoBio/purell v1.2.0
github.com/alecthomas/assert/v2 v2.2.1
github.com/alecthomas/chroma/v2 v2.9.1 github.com/alecthomas/chroma/v2 v2.9.1
github.com/caarlos0/env v3.5.0+incompatible
github.com/charmbracelet/bubbles v0.20.0 github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.2.4 github.com/charmbracelet/bubbletea v1.2.4
github.com/charmbracelet/lipgloss v1.0.0 github.com/charmbracelet/lipgloss v1.0.0
github.com/charmbracelet/log v0.4.0 github.com/charmbracelet/log v0.4.0
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-billy/v5 v5.6.0 github.com/go-git/go-git/v5 v5.12.0
github.com/go-git/go-git/v5 v5.13.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/goreleaser/nfpm/v2 v2.41.0 github.com/goreleaser/nfpm/v2 v2.41.0
github.com/hashicorp/go-hclog v0.14.1
github.com/hashicorp/go-plugin v1.6.3
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
github.com/leonelquinteros/gotext v1.7.0 github.com/leonelquinteros/gotext v1.7.0
@ -31,14 +26,13 @@ require (
github.com/muesli/reflow v0.3.0 github.com/muesli/reflow v0.3.0
github.com/pelletier/go-toml/v2 v2.1.0 github.com/pelletier/go-toml/v2 v2.1.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
github.com/urfave/cli/v2 v2.25.7 github.com/urfave/cli/v2 v2.25.7
github.com/vmihailenco/msgpack/v5 v5.3.5 github.com/vmihailenco/msgpack/v5 v5.3.5
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.27.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
golang.org/x/sys v0.31.0 golang.org/x/sys v0.29.0
golang.org/x/text v0.23.0 golang.org/x/text v0.21.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.25.0 modernc.org/sqlite v1.25.0
mvdan.cc/sh/v3 v3.10.0 mvdan.cc/sh/v3 v3.10.0
@ -51,8 +45,7 @@ require (
github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/alecthomas/repr v0.2.0 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect github.com/andybalholm/brotli v1.0.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
@ -66,31 +59,24 @@ require (
github.com/cloudflare/circl v1.3.8 // indirect github.com/cloudflare/circl v1.3.8 // indirect
github.com/connesc/cipherio v0.2.1 // indirect github.com/connesc/cipherio v0.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/creack/pty v1.1.24 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect github.com/dsnet/compress v0.0.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/efficientgo/core v1.0.0-rc.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect
github.com/google/uuid v1.4.0 // indirect github.com/google/uuid v1.4.0 // indirect
github.com/goreleaser/chglog v0.6.1 // indirect github.com/goreleaser/chglog v0.6.1 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/huandu/xstrings v1.3.3 // indirect github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.16 // indirect github.com/imdario/mergo v0.3.16 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
@ -109,7 +95,6 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
@ -118,7 +103,7 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shopspring/decimal v1.2.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect github.com/skeema/knownhosts v1.2.2 // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cast v1.6.0 // indirect
github.com/therootcompany/xz v1.0.1 // indirect github.com/therootcompany/xz v1.0.1 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect github.com/ulikunitz/xz v0.5.12 // indirect
@ -127,14 +112,11 @@ require (
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/mod v0.19.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.38.0 // indirect golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.12.0 // indirect golang.org/x/sync v0.10.0 // indirect
golang.org/x/term v0.30.0 // indirect golang.org/x/term v0.28.0 // indirect
golang.org/x/tools v0.23.0 // indirect golang.org/x/tools v0.22.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.58.3 // indirect
google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
lukechampine.com/uint128 v1.2.0 // indirect lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect

153
go.sum

@ -17,8 +17,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8= gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1 h1:c7F4OsyQbiVpSOrYGMrNsRL37BwoOfrgoKxAwULBKZo=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw= gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
@ -39,8 +39,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s= github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
@ -61,8 +61,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
@ -71,17 +69,12 @@ github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY
github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM= github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM=
github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA= github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
@ -100,6 +93,7 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI= github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
@ -107,10 +101,10 @@ github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoV
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -121,32 +115,26 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/efficientgo/core v1.0.0-rc.0 h1:jJoA0N+C4/knWYVZ6GrdHOtDyrg8Y/TR4vFpTaqTsqs= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/efficientgo/core v1.0.0-rc.0/go.mod h1:kQa0V74HNYMfuJH6jiPiwNdpWXl4xd/K4tzlrcvYDQI= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 h1:C/FNIs+MtAJgQYLJ9FX/ACFYyDRuLYoXTmueErrOJyA=
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0/go.mod h1:plsKU0YHE9uX+7utvr7SiDtVBSHJyEfHRO4UnUgDmts=
github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug=
github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
@ -173,9 +161,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -184,11 +169,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -215,16 +197,10 @@ github.com/goreleaser/nfpm/v2 v2.41.0 h1:JyMzS/EwqaWbFs+7Z9oZ4Hkk4or00gUTqwm9Dgr
github.com/goreleaser/nfpm/v2 v2.41.0/go.mod h1:VPc5kF5OgfA+BosV/A2aB+Vg34honjWvp0Vt8ogsSi0= github.com/goreleaser/nfpm/v2 v2.41.0/go.mod h1:VPc5kF5OgfA+BosV/A2aB+Vg34honjWvp0Vt8ogsSi0=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg=
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
@ -239,12 +215,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc= github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@ -276,11 +248,9 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
@ -292,8 +262,6 @@ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
@ -314,14 +282,10 @@ 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/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk= github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
@ -332,15 +296,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.36.0 h1:78hJTing+BLYLjhXE+Z2BubeEymH5Lr0/Mt8FKkxxYo=
github.com/prometheus/common v0.36.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@ -361,8 +317,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
@ -382,8 +338,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns=
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
@ -419,8 +373,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -429,8 +385,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -449,8 +405,9 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -469,15 +426,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -485,8 +442,9 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -497,7 +455,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -513,14 +470,18 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -529,8 +490,10 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -558,8 +521,9 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -576,8 +540,6 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -591,8 +553,6 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -600,12 +560,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -616,7 +570,6 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

@ -30,7 +30,6 @@ import (
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
@ -72,17 +71,19 @@ func HelperCmd() *cli.Command {
helper, ok := helpers.Helpers[c.Args().First()] helper, ok := helpers.Helpers[c.Args().First()]
if !ok { if !ok {
slog.Error(gotext.Get("No such helper command"), "name", c.Args().First()) slog.Error(gotext.Get("No such helper command"), "name", c.Args().First())
return cli.Exit(gotext.Get("No such helper command"), 1) os.Exit(1)
} }
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
} }
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
} }
hc := interp.HandlerContext{ hc := interp.HandlerContext{

99
info.go

@ -21,19 +21,19 @@ package main
import ( import (
"fmt" "fmt"
"log/slog"
"os" "os"
"github.com/jeandeaual/go-locale"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" "gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func InfoCmd() *cli.Command { func InfoCmd() *cli.Command {
@ -47,71 +47,40 @@ func InfoCmd() *cli.Command {
Usage: gotext.Get("Show all information, not just for the current distro"), Usage: gotext.Get("Show all information, not just for the current distro"),
}, },
}, },
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
Build()
if err != nil {
return err
}
defer deps.Defer()
result, err := deps.DB.GetPkgs(c.Context, "true")
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
}
defer result.Close()
for result.Next() {
var pkg database.Package
err = result.StructScan(&pkg)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err)
}
fmt.Println(pkg.Name)
}
return nil
}),
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { ctx := c.Context
return err
cfg := config.New()
db := db.New(cfg)
err := db.Init(ctx)
if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
} }
rs := repos.New(cfg, db)
args := c.Args() args := c.Args()
if args.Len() < 1 { if args.Len() < 1 {
return cli.Exit(gotext.Get("Command info expected at least 1 argument, got %d", args.Len()), 1) slog.Error(gotext.Get("Command info expected at least 1 argument, got %d", args.Len()))
os.Exit(1)
} }
ctx := c.Context if cfg.AutoPull(ctx) {
err := rs.Pull(ctx, cfg.Repos(ctx))
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithRepos().
Build()
if err != nil { if err != nil {
return cli.Exit(err, 1) slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1)
}
} }
defer deps.Defer()
rs := deps.Repos
found, _, err := rs.FindPkgs(ctx, args.Slice()) found, _, err := rs.FindPkgs(ctx, args.Slice())
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error finding packages"), err) slog.Error(gotext.Get("Error finding packages"), "err", err)
os.Exit(1)
} }
if len(found) == 0 { if len(found) == 0 {
return cliutils.FormatCliExit(gotext.Get("Package not found"), err) os.Exit(1)
} }
pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive")) pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive"))
@ -119,26 +88,20 @@ func InfoCmd() *cli.Command {
var names []string var names []string
all := c.Bool("all") all := c.Bool("all")
systemLang, err := locale.GetLanguage()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Can't detect system language"), err)
}
if systemLang == "" {
systemLang = "en"
}
if !all { if !all {
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) slog.Error(gotext.Get("Error parsing os-release file"), "err", err)
os.Exit(1)
} }
names, err = overrides.Resolve( names, err = overrides.Resolve(
info, info,
overrides.DefaultOpts. overrides.DefaultOpts.
WithLanguages([]string{systemLang}), WithLanguages([]string{config.SystemLang()}),
) )
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err) slog.Error(gotext.Get("Error resolving overrides"), "err", err)
os.Exit(1)
} }
} }
@ -146,12 +109,14 @@ func InfoCmd() *cli.Command {
if !all { if !all {
err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names)) err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) slog.Error(gotext.Get("Error encoding script variables"), "err", err)
os.Exit(1)
} }
} else { } else {
err = yaml.NewEncoder(os.Stdout).Encode(pkg) err = yaml.NewEncoder(os.Stdout).Encode(pkg)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) slog.Error(gotext.Get("Error encoding script variables"), "err", err)
os.Exit(1)
} }
} }

@ -21,17 +21,20 @@ package main
import ( import (
"fmt" "fmt"
"log/slog"
"os"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func InstallCmd() *cli.Command { func InstallCmd() *cli.Command {
@ -43,102 +46,83 @@ func InstallCmd() *cli.Command {
&cli.BoolFlag{ &cli.BoolFlag{
Name: "clean", Name: "clean",
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: gotext.Get("Build package from scratch even if there's an already built package available"), Usage: "Build package from scratch even if there's an already built package available",
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfNotRoot(); err != nil { ctx := c.Context
return err
}
args := c.Args() args := c.Args()
if args.Len() < 1 { if args.Len() < 1 {
return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil) slog.Error(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()))
os.Exit(1)
} }
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { mgr := manager.Detect()
return err if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
} }
installer, installerClose, err := build.GetSafeInstaller() cfg := config.New()
db := database.New(cfg)
rs := repos.New(cfg, db)
err := db.Init(ctx)
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error db init"), "err", err)
} os.Exit(1)
defer installerClose()
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
return err
} }
scripter, scripterClose, err := build.GetSafeScriptExecutor() if cfg.AutoPull(ctx) {
err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1)
}
} }
defer scripterClose()
ctx := c.Context found, notFound, err := rs.FindPkgs(ctx, args.Slice())
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithRepos().
WithDistroInfo().
WithManager().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error finding packages"), "err", err)
} os.Exit(1)
defer deps.Defer()
builder, err := build.NewMainBuilder(
deps.Cfg,
deps.Manager,
deps.Repos,
scripter,
installer,
)
if err != nil {
return err
} }
err = builder.InstallPkgs( pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive"))
ctx,
&build.BuildArgs{ opts := types.BuildOpts{
Opts: &types.BuildOpts{ Manager: mgr,
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
},
Info: deps.Info,
PkgFormat_: build.GetPkgFormat(deps.Manager),
},
args.Slice(),
)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error parsing os release"), err)
} }
info, err := distro.ParseOSRelease(ctx)
if err != nil {
slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1)
}
builder := build.NewBuilder(
ctx,
opts,
rs,
info,
cfg,
)
builder.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
})
return nil return nil
}, },
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { BashComplete: func(c *cli.Context) {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { cfg := config.New()
return err db := database.New(cfg)
} result, err := db.GetPkgs(c.Context, "true")
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error getting packages"), "err", err)
} os.Exit(1)
defer deps.Defer()
result, err := deps.DB.GetPkgs(c.Context, "true")
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
} }
defer result.Close() defer result.Close()
@ -146,14 +130,13 @@ func InstallCmd() *cli.Command {
var pkg database.Package var pkg database.Package
err = result.StructScan(&pkg) err = result.StructScan(&pkg)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) slog.Error(gotext.Get("Error iterating over packages"), "err", err)
os.Exit(1)
} }
fmt.Println(pkg.Name) fmt.Println(pkg.Name)
} }
},
return nil
}),
} }
} }
@ -162,80 +145,23 @@ func RemoveCmd() *cli.Command {
Name: "remove", Name: "remove",
Usage: gotext.Get("Remove an installed package"), Usage: gotext.Get("Remove an installed package"),
Aliases: []string{"rm"}, Aliases: []string{"rm"},
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithManager().
Build()
if err != nil {
return cli.Exit(err, 1)
}
defer deps.Defer()
installedAlrPackages := map[string]string{}
installed, err := deps.Manager.ListInstalled(&manager.Opts{})
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error listing installed packages"), err)
}
for pkgName, version := range installed {
matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName)
if matches != nil {
packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")]
repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")]
installedAlrPackages[fmt.Sprintf("%s/%s", repoName, packageName)] = version
}
}
result, err := deps.DB.GetPkgs(c.Context, "true")
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
}
defer result.Close()
for result.Next() {
var pkg database.Package
err = result.StructScan(&pkg)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err)
}
_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
if !ok {
continue
}
fmt.Println(pkg.Name)
}
return nil
}),
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfNotRoot(); err != nil {
return err
}
args := c.Args() args := c.Args()
if args.Len() < 1 { if args.Len() < 1 {
return cliutils.FormatCliExit(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len()), nil) slog.Error(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len()))
os.Exit(1)
} }
deps, err := appbuilder. mgr := manager.Detect()
New(c.Context). if mgr == nil {
WithManager(). slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
Build() os.Exit(1)
}
err := mgr.Remove(nil, c.Args().Slice()...)
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error removing packages"), "err", err)
} os.Exit(1)
defer deps.Defer()
if err := deps.Manager.Remove(&manager.Opts{
NoConfirm: !c.Bool("interactive"),
}, c.Args().Slice()...); err != nil {
return cliutils.FormatCliExit(gotext.Get("Error removing packages"), err)
} }
return nil return nil

@ -1,280 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 (
"bufio"
"errors"
"fmt"
"log/slog"
"os"
"os/exec"
"os/user"
"path/filepath"
"syscall"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
func InternalBuildCmd() *cli.Command {
return &cli.Command{
Name: "_internal-safe-script-executor",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid())
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
cfg := config.New()
err := cfg.Load()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
}
logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Output: os.Stderr,
Level: hclog.Debug,
JSONFormat: false,
DisableTime: true,
})
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: build.HandshakeConfig,
Plugins: map[string]plugin.Plugin{
"script-executor": &build.ScriptExecutorPlugin{
Impl: build.NewLocalScriptExecutor(cfg),
},
},
Logger: logger,
})
return nil
},
}
}
func InternalInstallCmd() *cli.Command {
return &cli.Command{
Name: "_internal-installer",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
if err := utils.EnsureIsAlrUser(); err != nil {
return err
}
// Before escalating the rights, we made sure that
// this is an ALR user, so it looks safe.
err := utils.EscalateToRootUid()
if err != nil {
return cliutils.FormatCliExit("cannot escalate to root", err)
}
deps, err := appbuilder.
New(c.Context).
WithConfig().
WithDB().
WithReposNoPull().
Build()
if err != nil {
return err
}
defer deps.Defer()
logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Output: os.Stderr,
Level: hclog.Trace,
JSONFormat: true,
DisableTime: true,
})
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: build.HandshakeConfig,
Plugins: map[string]plugin.Plugin{
"installer": &build.InstallerPlugin{
Impl: build.NewInstaller(
manager.Detect(),
),
},
},
Logger: logger,
})
return nil
},
}
}
func Mount(target string) (string, func(), error) {
exe, err := os.Executable()
if err != nil {
return "", nil, fmt.Errorf("failed to get executable path: %w", err)
}
cmd := exec.Command(exe, "_internal-temporary-mount", target)
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return "", nil, fmt.Errorf("failed to get stdout pipe: %w", err)
}
stdinPipe, err := cmd.StdinPipe()
if err != nil {
return "", nil, fmt.Errorf("failed to get stdin pipe: %w", err)
}
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return "", nil, fmt.Errorf("failed to start mount: %w", err)
}
scanner := bufio.NewScanner(stdoutPipe)
var mountPath string
if scanner.Scan() {
mountPath = scanner.Text()
}
if err := scanner.Err(); err != nil {
_ = cmd.Process.Kill()
return "", nil, fmt.Errorf("failed to read mount output: %w", err)
}
if mountPath == "" {
_ = cmd.Process.Kill()
return "", nil, errors.New("mount failed: no target path returned")
}
cleanup := func() {
slog.Debug("cleanup triggered")
_, _ = fmt.Fprintln(stdinPipe, "")
_ = cmd.Wait()
}
return mountPath, cleanup, nil
}
func InternalMountCmd() *cli.Command {
return &cli.Command{
Name: "_internal-temporary-mount",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
sourceDir := c.Args().First()
u, err := user.Current()
if err != nil {
return cliutils.FormatCliExit("cannot get current user", err)
}
_, alrGid, err := utils.GetUidGidAlrUser()
if err != nil {
return cliutils.FormatCliExit("cannot get alr user", err)
}
if _, err := os.Stat(sourceDir); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("cannot read %s", sourceDir), err)
}
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil {
return err
}
// Before escalating the rights, we made sure that
// 1. user in wheel group
// 2. user can access sourceDir
if err := utils.EscalateToRootUid(); err != nil {
return err
}
if err := syscall.Setgid(alrGid); err != nil {
return err
}
if err := os.MkdirAll(constants.AlrRunDir, 0o770); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("failed to create %s", constants.AlrRunDir), err)
}
if err := os.Chown(constants.AlrRunDir, 0, alrGid); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("failed to chown %s", constants.AlrRunDir), err)
}
targetDir := filepath.Join(constants.AlrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid()))
// 0750: owner (root) and group (alr)
if err := os.MkdirAll(targetDir, 0o750); err != nil {
return cliutils.FormatCliExit("error creating bindfs target directory", err)
}
// chown AlrRunDir/mounts/bindfs-* to (root:alr),
// so alr user can access dir
if err := os.Chown(targetDir, 0, alrGid); err != nil {
return cliutils.FormatCliExit("failed to chown bindfs directory", err)
}
bindfsCmd := exec.Command(
"bindfs",
fmt.Sprintf("--map=%s/alr:@%s/@alr", u.Uid, u.Gid),
sourceDir,
targetDir,
)
bindfsCmd.Stderr = os.Stderr
if err := bindfsCmd.Run(); err != nil {
return cliutils.FormatCliExit("failed to strart bindfs", err)
}
fmt.Println(targetDir)
_, _ = bufio.NewReader(os.Stdin).ReadString('\n')
slog.Debug("start unmount", "dir", targetDir)
umountCmd := exec.Command("umount", targetDir)
umountCmd.Stderr = os.Stderr
if err := umountCmd.Run(); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("failed to unmount %s", targetDir), err)
}
if err := os.Remove(targetDir); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("error removing directory %s", targetDir), err)
}
return nil
},
}
}

@ -1,176 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 appbuilder
import (
"context"
"errors"
"log/slog"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
)
type AppDeps struct {
Cfg *config.ALRConfig
DB *db.Database
Repos *repos.Repos
Info *distro.OSRelease
Manager manager.Manager
}
func (d *AppDeps) Defer() {
if d.DB != nil {
if err := d.DB.Close(); err != nil {
slog.Warn("failed to close db", "err", err)
}
}
}
type AppBuilder struct {
deps AppDeps
err error
ctx context.Context
}
func New(ctx context.Context) *AppBuilder {
return &AppBuilder{ctx: ctx}
}
func (b *AppBuilder) UseConfig(cfg *config.ALRConfig) *AppBuilder {
if b.err != nil {
return b
}
b.deps.Cfg = cfg
return b
}
func (b *AppBuilder) WithConfig() *AppBuilder {
if b.err != nil {
return b
}
cfg := config.New()
if err := cfg.Load(); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
return b
}
b.deps.Cfg = cfg
return b
}
func (b *AppBuilder) WithDB() *AppBuilder {
if b.err != nil {
return b
}
cfg := b.deps.Cfg
if cfg == nil {
b.err = errors.New("config is required before initializing DB")
return b
}
db := db.New(cfg)
if err := db.Init(b.ctx); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error initialization database"), err)
return b
}
b.deps.DB = db
return b
}
func (b *AppBuilder) WithRepos() *AppBuilder {
b.withRepos(true, false)
return b
}
func (b *AppBuilder) WithReposForcePull() *AppBuilder {
b.withRepos(true, true)
return b
}
func (b *AppBuilder) WithReposNoPull() *AppBuilder {
b.withRepos(false, false)
return b
}
func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder {
if b.err != nil {
return b
}
cfg := b.deps.Cfg
db := b.deps.DB
if cfg == nil || db == nil {
b.err = errors.New("config and db are required before initializing repos")
return b
}
rs := repos.New(cfg, db)
if enablePull && (forcePull || cfg.AutoPull()) {
if err := rs.Pull(b.ctx, cfg.Repos()); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error pulling repositories"), err)
return b
}
}
b.deps.Repos = rs
return b
}
func (b *AppBuilder) WithDistroInfo() *AppBuilder {
if b.err != nil {
return b
}
b.deps.Info, b.err = distro.ParseOSRelease(b.ctx)
if b.err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error parsing os release"), b.err)
}
return b
}
func (b *AppBuilder) WithManager() *AppBuilder {
if b.err != nil {
return b
}
b.deps.Manager = manager.Detect()
if b.deps.Manager == nil {
b.err = cliutils.FormatCliExit(gotext.Get("Unable to detect a supported package manager on the system"), nil)
}
return b
}
func (b *AppBuilder) Build() (*AppDeps, error) {
if b.err != nil {
return nil, b.err
}
return &b.deps, nil
}

@ -1,102 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cliutils
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
// Templates are based on https://github.com/urfave/cli/blob/3b17080d70a630feadadd23dd036cad121dd9a50/template.go
//nolint:unused
var (
helpNameTemplate = `{{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}`
descriptionTemplate = `{{wrap .Description 3}}`
authorsTemplate = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}`
visibleCommandTemplate = `{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}`
visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}`
visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}}
{{if .Name}}{{.Name}}
{{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}}
{{else}}{{$e}}
{{end}}{{end}}{{end}}`
visibleFlagTemplate = `{{range $i, $e := .VisibleFlags}}
{{wrap $e.String 6}}{{end}}`
copyrightTemplate = `{{wrap .Copyright 3}}`
)
func GetAppCliTemplate() string {
return fmt.Sprintf(`%s:
{{template "helpNameTemplate" .}}
%s:
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[%s]{{end}}{{if .Commands}} %s [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
%s:
{{.Version}}{{end}}{{end}}{{if .Description}}
%s:
{{template "descriptionTemplate" .}}{{end}}
{{- if len .Authors}}
%s{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}}
%s:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
%s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
%s:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}}
%s:
{{template "copyrightTemplate" .}}{{end}}
`, gotext.Get("NAME"), gotext.Get("USAGE"), gotext.Get("global options"), gotext.Get("command"), gotext.Get("command options"), gotext.Get("arguments"), gotext.Get("VERSION"), gotext.Get("DESCRIPTION"), gotext.Get("AUTHOR"), gotext.Get("COMMANDS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("COPYRIGHT"))
}
func GetCommandHelpTemplate() string {
return fmt.Sprintf(`%s:
{{template "helpNameTemplate" .}}
%s:
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Category}}
%s:
{{.Category}}{{end}}{{if .Description}}
%s:
{{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
%s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
%s:{{template "visibleFlagTemplate" .}}{{end}}
`, gotext.Get("NAME"),
gotext.Get("USAGE"),
gotext.Get("command options"),
gotext.Get("arguments"),
gotext.Get("CATEGORY"),
gotext.Get("DESCRIPTION"),
gotext.Get("OPTIONS"),
gotext.Get("OPTIONS"),
)
}

@ -1,63 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cliutils
import (
"errors"
"fmt"
"log/slog"
"github.com/urfave/cli/v2"
)
type BashCompleteWithErrorFunc func(c *cli.Context) error
func BashCompleteWithError(f BashCompleteWithErrorFunc) cli.BashCompleteFunc {
return func(c *cli.Context) { HandleExitCoder(f(c)) }
}
func HandleExitCoder(err error) {
if err == nil {
return
}
if exitErr, ok := err.(cli.ExitCoder); ok {
if err.Error() != "" {
if _, ok := exitErr.(cli.ErrorFormatter); ok {
slog.Error(fmt.Sprintf("%+v\n", err))
} else {
slog.Error(err.Error())
}
}
cli.OsExiter(exitErr.ExitCode())
return
}
slog.Error(err.Error())
cli.OsExiter(1)
}
func FormatCliExit(msg string, err error) cli.ExitCoder {
return FormatCliExitWithCode(msg, err, 1)
}
func FormatCliExitWithCode(msg string, err error, exitCode int) cli.ExitCoder {
if err == nil {
return cli.Exit(errors.New(msg), exitCode)
}
return cli.Exit(fmt.Errorf("%s: %w", msg, err), exitCode)
}

@ -20,28 +20,32 @@
package config package config
import ( import (
"context"
"log/slog" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "sync"
"github.com/caarlos0/env"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" "github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
) )
type ALRConfig struct { type ALRConfig struct {
cfg *types.Config cfg *types.Config
paths *Paths paths *Paths
cfgOnce sync.Once
pathsOnce sync.Once
} }
var defaultConfig = &types.Config{ var defaultConfig = &types.Config{
RootCmd: "sudo", RootCmd: "sudo",
PagerStyle: "native", PagerStyle: "native",
IgnorePkgUpdates: []string{}, IgnorePkgUpdates: []string{},
AutoPull: true, AutoPull: false,
Repos: []types.Repo{}, Repos: []types.Repo{},
} }
@ -49,108 +53,122 @@ func New() *ALRConfig {
return &ALRConfig{} return &ALRConfig{}
} }
func readConfig(path string) (*types.Config, error) { func (c *ALRConfig) Load(ctx context.Context) {
file, err := os.Open(path) cfgFl, err := os.Open(c.GetPaths(ctx).ConfigPath)
if err != nil { if err != nil {
return nil, err slog.Warn(gotext.Get("Error opening config file, using defaults"), "err", err)
} c.cfg = defaultConfig
defer file.Close()
config := types.Config{}
if err := toml.NewDecoder(file).Decode(&config); err != nil {
return nil, err
}
return &config, nil
}
func mergeStructs(dst, src interface{}) {
srcVal := reflect.ValueOf(src)
if srcVal.IsNil() {
return return
} }
srcVal = srcVal.Elem() defer cfgFl.Close()
dstVal := reflect.ValueOf(dst).Elem()
for i := range srcVal.NumField() { // Copy the default configuration into config
srcField := srcVal.Field(i) defCopy := *defaultConfig
srcFieldName := srcVal.Type().Field(i).Name config := &defCopy
config.Repos = nil
dstField := dstVal.FieldByName(srcFieldName) err = toml.NewDecoder(cfgFl).Decode(config)
if dstField.IsValid() && dstField.CanSet() {
dstField.Set(srcField)
}
}
}
func (c *ALRConfig) Load() error {
systemConfig, err := readConfig(
constants.SystemConfigPath,
)
if err != nil { if err != nil {
slog.Debug("Cannot read system config", "err", err) slog.Warn(gotext.Get("Error decoding config file, using defaults"), "err", err)
c.cfg = defaultConfig
return
} }
config := &types.Config{}
mergeStructs(config, defaultConfig)
mergeStructs(config, systemConfig)
err = env.Parse(config)
if err != nil {
return err
}
c.cfg = config c.cfg = config
c.paths = &Paths{}
c.paths.UserConfigPath = constants.SystemConfigPath
c.paths.CacheDir = constants.SystemCachePath
c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo")
c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs")
c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db")
// c.initPaths()
return nil
} }
func (c *ALRConfig) RootCmd() string { func (c *ALRConfig) initPaths() {
return c.cfg.RootCmd paths := &Paths{}
cfgDir, err := os.UserConfigDir()
if err != nil {
slog.Error(gotext.Get("Unable to detect user config directory"), "err", err)
os.Exit(1)
}
paths.ConfigDir = filepath.Join(cfgDir, "alr")
err = os.MkdirAll(paths.ConfigDir, 0o755)
if err != nil {
slog.Error(gotext.Get("Unable to create ALR config directory"), "err", err)
os.Exit(1)
}
paths.ConfigPath = filepath.Join(paths.ConfigDir, "alr.toml")
if _, err := os.Stat(paths.ConfigPath); err != nil {
cfgFl, err := os.Create(paths.ConfigPath)
if err != nil {
slog.Error(gotext.Get("Unable to create ALR config file"), "err", err)
os.Exit(1)
}
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
if err != nil {
slog.Error(gotext.Get("Error encoding default configuration"), "err", err)
os.Exit(1)
}
cfgFl.Close()
}
cacheDir, err := os.UserCacheDir()
if err != nil {
slog.Error(gotext.Get("Unable to detect cache directory"), "err", err)
os.Exit(1)
}
paths.CacheDir = filepath.Join(cacheDir, "alr")
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
err = os.MkdirAll(paths.RepoDir, 0o755)
if err != nil {
slog.Error(gotext.Get("Unable to create repo cache directory"), "err", err)
os.Exit(1)
}
err = os.MkdirAll(paths.PkgsDir, 0o755)
if err != nil {
slog.Error(gotext.Get("Unable to create package cache directory"), "err", err)
os.Exit(1)
}
paths.DBPath = filepath.Join(paths.CacheDir, "db")
c.paths = paths
} }
func (c *ALRConfig) PagerStyle() string { func (c *ALRConfig) GetPaths(ctx context.Context) *Paths {
return c.cfg.PagerStyle c.pathsOnce.Do(func() {
} c.initPaths()
})
func (c *ALRConfig) AutoPull() bool {
return c.cfg.AutoPull
}
func (c *ALRConfig) Repos() []types.Repo {
return c.cfg.Repos
}
func (c *ALRConfig) SetRepos(repos []types.Repo) {
c.cfg.Repos = repos
}
func (c *ALRConfig) IgnorePkgUpdates() []string {
return c.cfg.IgnorePkgUpdates
}
func (c *ALRConfig) LogLevel() string {
return c.cfg.LogLevel
}
func (c *ALRConfig) GetPaths() *Paths {
return c.paths return c.paths
} }
func (c *ALRConfig) SaveUserConfig() error { func (c *ALRConfig) Repos(ctx context.Context) []types.Repo {
f, err := os.Create(c.paths.UserConfigPath) c.cfgOnce.Do(func() {
if err != nil { c.Load(ctx)
return err })
} return c.cfg.Repos
}
return toml.NewEncoder(f).Encode(c.cfg)
func (c *ALRConfig) IgnorePkgUpdates(ctx context.Context) []string {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.IgnorePkgUpdates
}
func (c *ALRConfig) AutoPull(ctx context.Context) bool {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.AutoPull
}
func (c *ALRConfig) PagerStyle(ctx context.Context) string {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.PagerStyle
} }

@ -14,26 +14,39 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package finddeps package config
import ( import (
"context" "context"
"log/slog" "sync"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
) )
type EmptyFindProvReq struct{} // Config returns a ALR configuration struct.
// The first time it's called, it'll load the config from a file.
func (o *EmptyFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { // Subsequent calls will just return the same value.
slog.Info(gotext.Get("AutoProv is not implemented for this package format, so it's skipped")) //
return nil // Deprecated: use struct method
func Config(ctx context.Context) *types.Config {
return GetInstance(ctx).cfg
} }
func (o *EmptyFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { // =======================
slog.Info(gotext.Get("AutoReq is not implemented for this package format, so it's skipped")) // FOR LEGACY ONLY
return nil // =======================
var (
alrConfig *ALRConfig
alrConfigOnce sync.Once
)
// Deprecated: For legacy only
func GetInstance(ctx context.Context) *ALRConfig {
alrConfigOnce.Do(func() {
alrConfig = New()
alrConfig.Load(ctx)
})
return alrConfig
} }

70
internal/config/lang.go Normal file

@ -0,0 +1,70 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config
import (
"context"
"log/slog"
"os"
"strings"
"sync"
"github.com/leonelquinteros/gotext"
"golang.org/x/text/language"
)
var (
langMtx sync.Mutex
lang language.Tag
langSet bool
)
// Language returns the system language.
// The first time it's called, it'll detect the langauge based on
// the $LANG environment variable.
// Subsequent calls will just return the same value.
func Language(ctx context.Context) language.Tag {
langMtx.Lock()
if !langSet {
syslang := SystemLang()
tag, err := language.Parse(syslang)
if err != nil {
slog.Error(gotext.Get("Error parsing system language"), "err", err)
langMtx.Unlock()
os.Exit(1)
}
base, _ := tag.Base()
lang = language.Make(base.String())
langSet = true
}
langMtx.Unlock()
return lang
}
// SystemLang returns the system language based on
// the $LANG environment variable.
func SystemLang() string {
lang := os.Getenv("LANG")
lang, _, _ = strings.Cut(lang, ".")
if lang == "" || lang == "C" {
lang = "en"
}
return lang
}

@ -19,11 +19,27 @@
package config package config
import (
"context"
)
// Paths contains various paths used by ALR // Paths contains various paths used by ALR
type Paths struct { type Paths struct {
UserConfigPath string ConfigDir string
ConfigPath string
CacheDir string CacheDir string
RepoDir string RepoDir string
PkgsDir string PkgsDir string
DBPath string DBPath string
} }
// GetPaths returns a Paths struct.
// The first time it's called, it'll generate the struct
// using information from the system.
// Subsequent calls will return the same value.
//
// Deprecated: use struct API
func GetPaths(ctx context.Context) *Paths {
alrConfig := GetInstance(ctx)
return alrConfig.GetPaths(ctx)
}

@ -1,24 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 constants
const (
SystemConfigPath = "/etc/alr/alr.toml"
SystemCachePath = "/var/cache/alr"
AlrRunDir = "/var/run/alr"
PrivilegedGroup = "wheel"
)

@ -31,7 +31,7 @@ import (
// CurrentVersion is the current version of the database. // CurrentVersion is the current version of the database.
// The database is reset if its version doesn't match this. // The database is reset if its version doesn't match this.
const CurrentVersion = 4 const CurrentVersion = 3
// Package is a ALR package's database representation // Package is a ALR package's database representation
type Package struct { type Package struct {
@ -40,9 +40,7 @@ type Package struct {
Version string `sh:"version,required" db:"version"` Version string `sh:"version,required" db:"version"`
Release int `sh:"release,required" db:"release"` Release int `sh:"release,required" db:"release"`
Epoch uint `sh:"epoch" db:"epoch"` Epoch uint `sh:"epoch" db:"epoch"`
Summary JSON[map[string]string] `db:"summary"`
Description JSON[map[string]string] `db:"description"` Description JSON[map[string]string] `db:"description"`
Group JSON[map[string]string] `db:"group_name"`
Homepage JSON[map[string]string] `db:"homepage"` Homepage JSON[map[string]string] `db:"homepage"`
Maintainer JSON[map[string]string] `db:"maintainer"` Maintainer JSON[map[string]string] `db:"maintainer"`
Architectures JSON[[]string] `sh:"architectures" db:"architectures"` Architectures JSON[[]string] `sh:"architectures" db:"architectures"`
@ -61,7 +59,7 @@ type version struct {
} }
type Config interface { type Config interface {
GetPaths() *config.Paths GetPaths(ctx context.Context) *config.Paths
} }
type Database struct { type Database struct {
@ -84,7 +82,7 @@ func (d *Database) Init(ctx context.Context) error {
} }
func (d *Database) Connect(ctx context.Context) error { func (d *Database) Connect(ctx context.Context) error {
dsn := d.config.GetPaths().DBPath dsn := d.config.GetPaths(ctx).DBPath
db, err := sqlx.Open("sqlite", dsn) db, err := sqlx.Open("sqlite", dsn)
if err != nil { if err != nil {
return err return err
@ -108,9 +106,7 @@ func (d *Database) initDB(ctx context.Context) error {
version TEXT NOT NULL, version TEXT NOT NULL,
release INT NOT NULL, release INT NOT NULL,
epoch INT, epoch INT,
summary TEXT CHECK(summary = 'null' OR (JSON_VALID(summary) AND JSON_TYPE(summary) = 'object')),
description TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')), description TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')),
group_name TEXT CHECK(group_name = 'null' OR (JSON_VALID(group_name) AND JSON_TYPE(group_name) = 'object')),
homepage TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')), homepage TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')),
maintainer TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')), maintainer TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')),
architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')), architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')),
@ -208,9 +204,7 @@ func (d *Database) InsertPackage(ctx context.Context, pkg Package) error {
version, version,
release, release,
epoch, epoch,
summary,
description, description,
group_name,
homepage, homepage,
maintainer, maintainer,
architectures, architectures,
@ -228,9 +222,7 @@ func (d *Database) InsertPackage(ctx context.Context, pkg Package) error {
:version, :version,
:release, :release,
:epoch, :epoch,
:summary,
:description, :description,
:group_name,
:homepage, :homepage,
:maintainer, :maintainer,
:architectures, :architectures,

106
internal/db/db_legacy.go Normal file

@ -0,0 +1,106 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package db
import (
"context"
"log/slog"
"os"
"sync"
"github.com/jmoiron/sqlx"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
)
// DB returns the ALR database.
// The first time it's called, it opens the SQLite database file.
// Subsequent calls return the same connection.
//
// Deprecated: use struct method
func DB(ctx context.Context) *sqlx.DB {
return GetInstance(ctx).GetConn()
}
// Close closes the database
//
// Deprecated: use struct method
func Close() error {
if database != nil {
return database.Close()
}
return nil
}
// IsEmpty returns true if the database has no packages in it, otherwise it returns false.
//
// Deprecated: use struct method
func IsEmpty(ctx context.Context) bool {
return GetInstance(ctx).IsEmpty(ctx)
}
// InsertPackage adds a package to the database
//
// Deprecated: use struct method
func InsertPackage(ctx context.Context, pkg Package) error {
return GetInstance(ctx).InsertPackage(ctx, pkg)
}
// GetPkgs returns a result containing packages that match the where conditions
//
// Deprecated: use struct method
func GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) {
return GetInstance(ctx).GetPkgs(ctx, where, args...)
}
// GetPkg returns a single package that matches the where conditions
//
// Deprecated: use struct method
func GetPkg(ctx context.Context, where string, args ...any) (*Package, error) {
return GetInstance(ctx).GetPkg(ctx, where, args...)
}
// DeletePkgs deletes all packages matching the where conditions
//
// Deprecated: use struct method
func DeletePkgs(ctx context.Context, where string, args ...any) error {
return GetInstance(ctx).DeletePkgs(ctx, where, args...)
}
// =======================
// FOR LEGACY ONLY
// =======================
var (
dbOnce sync.Once
database *Database
)
// Deprecated: For legacy only
func GetInstance(ctx context.Context) *Database {
dbOnce.Do(func() {
cfg := config.GetInstance(ctx)
database = New(cfg)
err := database.Init(ctx)
if err != nil {
slog.Error(gotext.Get("Error opening database"), "err", err)
os.Exit(1)
}
})
return database
}

@ -33,7 +33,7 @@ import (
type TestALRConfig struct{} type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths() *config.Paths { func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
DBPath: ":memory:", DBPath: ":memory:",
} }

@ -38,7 +38,7 @@ import (
type TestALRConfig struct{} type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths() *config.Paths { func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
CacheDir: "/tmp", CacheDir: "/tmp",
} }
@ -91,7 +91,7 @@ func TestDownloadWithoutCache(t *testing.T) {
}, },
{ {
name: "git download", name: "git download",
path: "git+%s/git-downloader/git/Plemya-x/alr-repo", path: "git+%s/git-downloader/git/Plemya-x/xpamych-alr-repo",
expected: func(t *testing.T, err error, tmpdir string) { expected: func(t *testing.T, err error, tmpdir string) {
assert.NoError(t, err) assert.NoError(t, err)

@ -28,7 +28,7 @@ import (
) )
type Config interface { type Config interface {
GetPaths() *config.Paths GetPaths(ctx context.Context) *config.Paths
} }
type DownloadCache struct { type DownloadCache struct {
@ -43,7 +43,7 @@ func New(cfg Config) *DownloadCache {
func (dc *DownloadCache) BasePath(ctx context.Context) string { func (dc *DownloadCache) BasePath(ctx context.Context) string {
return filepath.Join( return filepath.Join(
dc.cfg.GetPaths().CacheDir, "dl", dc.cfg.GetPaths(ctx).CacheDir, "dl",
) )
} }

@ -32,11 +32,19 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" "gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
) )
func init() {
dir, err := os.MkdirTemp("/tmp", "alr-dlcache-test.*")
if err != nil {
panic(err)
}
config.GetPaths(context.Background()).RepoDir = dir
}
type TestALRConfig struct { type TestALRConfig struct {
CacheDir string CacheDir string
} }
func (c *TestALRConfig) GetPaths() *config.Paths { func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
CacheDir: c.CacheDir, CacheDir: c.CacheDir,
} }

@ -1,152 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 logger
import (
"io"
"log"
"strings"
chLog "github.com/charmbracelet/log"
"github.com/hashicorp/go-hclog"
)
type HCLoggerAdapter struct {
logger *Logger
}
func hclogLevelTochLog(level hclog.Level) chLog.Level {
switch level {
case hclog.Debug:
return chLog.DebugLevel
case hclog.Info:
return chLog.InfoLevel
case hclog.Warn:
return chLog.WarnLevel
case hclog.Error:
return chLog.ErrorLevel
}
return chLog.FatalLevel
}
func (a *HCLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{}) {
filteredArgs := make([]interface{}, 0, len(args))
for i := 0; i < len(args); i += 2 {
if i+1 >= len(args) {
filteredArgs = append(filteredArgs, args[i])
continue
}
key, ok := args[i].(string)
if !ok || key != "timestamp" {
filteredArgs = append(filteredArgs, args[i], args[i+1])
}
}
// Start ugly hacks
// Ignore exit messages
// - https://github.com/hashicorp/go-plugin/issues/331
// - https://github.com/hashicorp/go-plugin/issues/203
// - https://github.com/hashicorp/go-plugin/issues/192
var chLogLevel chLog.Level
if msg == "plugin process exited" ||
strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") ||
strings.HasPrefix(msg, "[DEBUG] plugin") {
chLogLevel = chLog.DebugLevel
} else {
chLogLevel = hclogLevelTochLog(level)
}
a.logger.l.Log(chLogLevel, msg, filteredArgs...)
}
func (a *HCLoggerAdapter) Trace(msg string, args ...interface{}) {
a.Log(hclog.Trace, msg, args...)
}
func (a *HCLoggerAdapter) Debug(msg string, args ...interface{}) {
a.Log(hclog.Debug, msg, args...)
}
func (a *HCLoggerAdapter) Info(msg string, args ...interface{}) {
a.Log(hclog.Info, msg, args...)
}
func (a *HCLoggerAdapter) Warn(msg string, args ...interface{}) {
a.Log(hclog.Warn, msg, args...)
}
func (a *HCLoggerAdapter) Error(msg string, args ...interface{}) {
a.Log(hclog.Error, msg, args...)
}
func (a *HCLoggerAdapter) IsTrace() bool {
return a.logger.l.GetLevel() <= chLog.DebugLevel
}
func (a *HCLoggerAdapter) IsDebug() bool {
return a.logger.l.GetLevel() <= chLog.DebugLevel
}
func (a *HCLoggerAdapter) IsInfo() bool {
return a.logger.l.GetLevel() <= chLog.InfoLevel
}
func (a *HCLoggerAdapter) IsWarn() bool {
return a.logger.l.GetLevel() <= chLog.WarnLevel
}
func (a *HCLoggerAdapter) IsError() bool {
return a.logger.l.GetLevel() <= chLog.ErrorLevel
}
func (a *HCLoggerAdapter) ImpliedArgs() []interface{} {
return nil
}
func (a *HCLoggerAdapter) With(args ...interface{}) hclog.Logger {
return a
}
func (a *HCLoggerAdapter) Name() string {
return ""
}
func (a *HCLoggerAdapter) Named(name string) hclog.Logger {
return a
}
func (a *HCLoggerAdapter) ResetNamed(name string) hclog.Logger {
return a
}
func (a *HCLoggerAdapter) SetLevel(level hclog.Level) {
}
func (a *HCLoggerAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger {
return nil
}
func (a *HCLoggerAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
return nil
}
func GetHCLoggerAdapter() *HCLoggerAdapter {
return &HCLoggerAdapter{
logger: logger,
}
}

@ -22,90 +22,75 @@ import (
"os" "os"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
chLog "github.com/charmbracelet/log"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
) )
type Logger struct { type Logger struct {
l *chLog.Logger lOut slog.Handler
lErr slog.Handler
} }
func setupLogger() *chLog.Logger { func setupOutLogger() *log.Logger {
styles := chLog.DefaultStyles() styles := log.DefaultStyles()
logger := chLog.New(os.Stderr) logger := log.New(os.Stdout)
styles.Levels[chLog.InfoLevel] = lipgloss.NewStyle(). styles.Levels[log.InfoLevel] = lipgloss.NewStyle().
SetString("-->"). SetString("-->").
Foreground(lipgloss.Color("35")) Foreground(lipgloss.Color("35"))
styles.Levels[chLog.ErrorLevel] = lipgloss.NewStyle(). logger.SetStyles(styles)
return logger
}
func setupErrorLogger() *log.Logger {
styles := log.DefaultStyles()
styles.Levels[log.ErrorLevel] = lipgloss.NewStyle().
SetString(gotext.Get("ERROR")). SetString(gotext.Get("ERROR")).
Padding(0, 1, 0, 1). Padding(0, 1, 0, 1).
Background(lipgloss.Color("204")). Background(lipgloss.Color("204")).
Foreground(lipgloss.Color("0")) Foreground(lipgloss.Color("0"))
logger := log.New(os.Stderr)
logger.SetStyles(styles) logger.SetStyles(styles)
return logger return logger
} }
func New() *Logger { func New() *Logger {
standardLogger := setupOutLogger()
errLogger := setupErrorLogger()
return &Logger{ return &Logger{
l: setupLogger(), lOut: standardLogger,
lErr: errLogger,
} }
} }
func slogLevelToLog(level slog.Level) chLog.Level {
switch level {
case slog.LevelDebug:
return chLog.DebugLevel
case slog.LevelInfo:
return chLog.InfoLevel
case slog.LevelWarn:
return chLog.WarnLevel
case slog.LevelError:
return chLog.ErrorLevel
}
return chLog.FatalLevel
}
func (l *Logger) SetLevel(level slog.Level) {
l.l.SetLevel(slogLevelToLog(level))
}
func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool { func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool {
return l.l.Enabled(ctx, level) if level <= slog.LevelInfo {
return l.lOut.Enabled(ctx, level)
}
return l.lErr.Enabled(ctx, level)
} }
func (l *Logger) Handle(ctx context.Context, rec slog.Record) error { func (l *Logger) Handle(ctx context.Context, rec slog.Record) error {
return l.l.Handle(ctx, rec) if rec.Level <= slog.LevelInfo {
return l.lOut.Handle(ctx, rec)
}
return l.lErr.Handle(ctx, rec)
} }
func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler { func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler {
sl := *l sl := *l
sl.l = l.l.WithAttrs(attrs).(*chLog.Logger) sl.lOut = l.lOut.WithAttrs(attrs)
sl.lErr = l.lErr.WithAttrs(attrs)
return &sl return &sl
} }
func (l *Logger) WithGroup(name string) slog.Handler { func (l *Logger) WithGroup(name string) slog.Handler {
sl := *l sl := *l
sl.l = l.l.WithGroup(name).(*chLog.Logger) sl.lOut = l.lOut.WithGroup(name)
sl.lErr = l.lErr.WithGroup(name)
return &sl return &sl
} }
var logger *Logger func SetupDefault() {
logger := slog.New(New())
func SetupDefault() *Logger { slog.SetDefault(logger)
logger = New()
slogLogger := slog.New(logger)
slog.SetDefault(slogLogger)
return logger
}
func SetupForGoPlugin() {
logger.l.SetFormatter(chLog.JSONFormatter)
chLog.TimestampKey = "@timestamp"
chLog.MessageKey = "@message"
chLog.LevelKey = "@level"
}
func GetLogger() *Logger {
return logger
} }

@ -157,8 +157,6 @@ type ResolvedPackage struct {
Version string `sh:"version"` Version string `sh:"version"`
Release int `sh:"release"` Release int `sh:"release"`
Epoch uint `sh:"epoch"` Epoch uint `sh:"epoch"`
Group string `db:"group_name"`
Summary string `db:"summary"`
Description string `db:"description"` Description string `db:"description"`
Homepage string `db:"homepage"` Homepage string `db:"homepage"`
Maintainer string `db:"maintainer"` Maintainer string `db:"maintainer"`

@ -41,7 +41,7 @@ func init() {
b2 := lipgloss.RoundedBorder() b2 := lipgloss.RoundedBorder()
b2.Left = "\u2524" b2.Left = "\u2524"
infoStyle = titleStyle.BorderStyle(b2) infoStyle = titleStyle.Copy().BorderStyle(b2)
} }
type Pager struct { type Pager struct {

@ -123,29 +123,15 @@ func (d *Decoder) DecodeVars(val any) error {
} }
rVal := reflect.ValueOf(val).Elem() rVal := reflect.ValueOf(val).Elem()
return d.decodeStruct(rVal)
}
func (d *Decoder) decodeStruct(rVal reflect.Value) error {
for i := 0; i < rVal.NumField(); i++ { for i := 0; i < rVal.NumField(); i++ {
field := rVal.Field(i) field := rVal.Field(i)
fieldType := rVal.Type().Field(i) fieldType := rVal.Type().Field(i)
// Пропускаем неэкспортируемые поля
if !fieldType.IsExported() { if !fieldType.IsExported() {
continue continue
} }
// Обрабатываем встроенные поля рекурсивно
if fieldType.Anonymous {
if field.Kind() == reflect.Struct {
if err := d.decodeStruct(field); err != nil {
return err
}
}
continue
}
name := fieldType.Name name := fieldType.Name
tag := fieldType.Tag.Get("sh") tag := fieldType.Tag.Get("sh")
required := false required := false
@ -174,6 +160,7 @@ func (d *Decoder) decodeStruct(rVal reflect.Value) error {
field.Set(newVal.Elem()) field.Set(newVal.Elem())
} }
return nil return nil
} }

@ -22,11 +22,10 @@ package handlers
import ( import (
"context" "context"
"io" "io"
"io/fs"
"os" "os"
) )
func NopReadDir(context.Context, string) ([]fs.DirEntry, error) { func NopReadDir(context.Context, string) ([]os.FileInfo, error) {
return nil, os.ErrNotExist return nil, os.ErrNotExist
} }

@ -31,12 +31,12 @@ import (
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
) )
func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc2 { func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc {
return func(ctx context.Context, s string) ([]fs.DirEntry, error) { return func(ctx context.Context, s string) ([]fs.FileInfo, error) {
path := filepath.Clean(s) path := filepath.Clean(s)
for _, allowedPrefix := range allowedPrefixes { for _, allowedPrefix := range allowedPrefixes {
if strings.HasPrefix(path, allowedPrefix) { if strings.HasPrefix(path, allowedPrefix) {
return interp.DefaultReadDirHandler2()(ctx, s) return interp.DefaultReadDirHandler()(ctx, s)
} }
} }

@ -9,83 +9,87 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: build.go:42 #: build.go:44
msgid "Build a local package" msgid "Build a local package"
msgstr "" msgstr ""
#: build.go:48 #: build.go:50
msgid "Path to the build script" msgid "Path to the build script"
msgstr "" msgstr ""
#: build.go:53 #: build.go:55
msgid "Specify subpackage in script (for multi package script only)" msgid "Specify package in script (for multi package script only)"
msgstr "" msgstr ""
#: build.go:58 #: build.go:60
msgid "Name of the package to build and its repo (example: default/go-bin)" msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr "" msgstr ""
#: build.go:63 #: build.go:65
msgid "" msgid ""
"Build package from scratch even if there's an already built package available" "Build package from scratch even if there's an already built package available"
msgstr "" msgstr ""
#: build.go:73 #: build.go:75
msgid "Error getting working directory" msgid "Error db init"
msgstr "" msgstr ""
#: build.go:118 #: build.go:105
msgid "Cannot get absolute script path"
msgstr ""
#: build.go:148
msgid "Package not found" msgid "Package not found"
msgstr "" msgstr ""
#: build.go:161 #: build.go:123
msgid "Nothing to build" msgid "Error pulling repositories"
msgstr "" msgstr ""
#: build.go:218 #: build.go:131
msgid "Unable to detect a supported package manager on the system"
msgstr ""
#: build.go:137
msgid "Error parsing os release"
msgstr ""
#: build.go:158
msgid "Error building package" msgid "Error building package"
msgstr "" msgstr ""
#: build.go:225 #: build.go:165
msgid "Error getting working directory"
msgstr ""
#: build.go:174
msgid "Error moving the package" msgid "Error moving the package"
msgstr "" msgstr ""
#: build.go:229 #: fix.go:37
msgid "Done"
msgstr ""
#: fix.go:38
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "" msgstr ""
#: fix.go:59 #: fix.go:44
msgid "Clearing cache directory" msgid "Removing cache directory"
msgstr "" msgstr ""
#: fix.go:64 #: fix.go:48
msgid "Unable to open cache directory" msgid "Unable to remove cache directory"
msgstr "" msgstr ""
#: fix.go:70 #: fix.go:52
msgid "Unable to read cache directory contents"
msgstr ""
#: fix.go:76
msgid "Unable to remove cache item (%s)"
msgstr ""
#: fix.go:80
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "" msgstr ""
#: fix.go:84 #: fix.go:56
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "" msgstr ""
#: fix.go:62
msgid "Error pulling repos"
msgstr ""
#: fix.go:66
msgid "Done"
msgstr ""
#: gen.go:34 #: gen.go:34
msgid "Generate a ALR script from a template" msgid "Generate a ALR script from a template"
msgstr "" msgstr ""
@ -94,26 +98,22 @@ msgstr ""
msgid "Generate a ALR script for a pip module" msgid "Generate a ALR script for a pip module"
msgstr "" msgstr ""
#: helper.go:42 #: helper.go:41
msgid "List all the available helper commands" msgid "List all the available helper commands"
msgstr "" msgstr ""
#: helper.go:54 #: helper.go:53
msgid "Run a ALR helper command" msgid "Run a ALR helper command"
msgstr "" msgstr ""
#: helper.go:61 #: helper.go:60
msgid "The directory that the install commands will install to" msgid "The directory that the install commands will install to"
msgstr "" msgstr ""
#: helper.go:74 helper.go:75 #: helper.go:73
msgid "No such helper command" msgid "No such helper command"
msgstr "" msgstr ""
#: helper.go:85
msgid "Error parsing os-release file"
msgstr ""
#: info.go:42 #: info.go:42
msgid "Print information about a package" msgid "Print information about a package"
msgstr "" msgstr ""
@ -122,76 +122,56 @@ msgstr ""
msgid "Show all information, not just for the current distro" msgid "Show all information, not just for the current distro"
msgstr "" msgstr ""
#: info.go:68 #: info.go:57
msgid "Error getting packages"
msgstr ""
#: info.go:76
msgid "Error iterating over packages"
msgstr ""
#: info.go:90
msgid "Command info expected at least 1 argument, got %d"
msgstr ""
#: info.go:110
msgid "Error finding packages"
msgstr ""
#: info.go:124
msgid "Can't detect system language"
msgstr ""
#: info.go:141
msgid "Error resolving overrides"
msgstr ""
#: info.go:149 info.go:154
msgid "Error encoding script variables"
msgstr ""
#: install.go:40
msgid "Install a new package"
msgstr ""
#: install.go:56
msgid "Command install expected at least 1 argument, got %d"
msgstr ""
#: install.go:118
msgid "Error parsing os release"
msgstr ""
#: install.go:163
msgid "Remove an installed package"
msgstr ""
#: install.go:182
msgid "Error listing installed packages"
msgstr ""
#: install.go:223
msgid "Command remove expected at least 1 argument, got %d"
msgstr ""
#: install.go:238
msgid "Error removing packages"
msgstr ""
#: internal/cliutils/app_builder/builder.go:75
msgid "Error loading config"
msgstr ""
#: internal/cliutils/app_builder/builder.go:96
msgid "Error initialization database" msgid "Error initialization database"
msgstr "" msgstr ""
#: internal/cliutils/app_builder/builder.go:135 #: info.go:64
msgid "Error pulling repositories" msgid "Command info expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: internal/cliutils/app_builder/builder.go:165 #: info.go:78
msgid "Unable to detect a supported package manager on the system" msgid "Error finding packages"
msgstr ""
#: info.go:94
msgid "Error parsing os-release file"
msgstr ""
#: info.go:103
msgid "Error resolving overrides"
msgstr ""
#: info.go:112 info.go:118
msgid "Error encoding script variables"
msgstr ""
#: install.go:43
msgid "Install a new package"
msgstr ""
#: install.go:57
msgid "Command install expected at least 1 argument, got %d"
msgstr ""
#: install.go:124
msgid "Error getting packages"
msgstr ""
#: install.go:133
msgid "Error iterating over packages"
msgstr ""
#: install.go:146
msgid "Remove an installed package"
msgstr ""
#: install.go:151
msgid "Command remove expected at least 1 argument, got %d"
msgstr ""
#: install.go:163
msgid "Error removing packages"
msgstr "" msgstr ""
#: internal/cliutils/prompt.go:60 #: internal/cliutils/prompt.go:60
@ -218,71 +198,59 @@ msgstr ""
msgid "Choose which optional package(s) to install" msgid "Choose which optional package(s) to install"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:93 #: internal/config/config.go:59
msgid "NAME" msgid "Error opening config file, using defaults"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:94 #: internal/config/config.go:72
msgid "USAGE" msgid "Error decoding config file, using defaults"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/config/config.go:84
msgid "global options" msgid "Unable to detect user config directory"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/config/config.go:92
msgid "command" msgid "Unable to create ALR config directory"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:95 #: internal/config/config.go:101
msgid "command options" msgid "Unable to create ALR config file"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:96 #: internal/config/config.go:107
msgid "arguments" msgid "Error encoding default configuration"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/config/config.go:116
msgid "VERSION" msgid "Unable to detect cache directory"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:98 #: internal/config/config.go:126
msgid "DESCRIPTION" msgid "Unable to create repo cache directory"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/config/config.go:132
msgid "AUTHOR" msgid "Unable to create package cache directory"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/config/lang.go:49
msgid "COMMANDS" msgid "Error parsing system language"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/db/db.go:133
msgid "GLOBAL OPTIONS"
msgstr ""
#: internal/cliutils/template.go:74
msgid "COPYRIGHT"
msgstr ""
#: internal/cliutils/template.go:97
msgid "CATEGORY"
msgstr ""
#: internal/cliutils/template.go:99 internal/cliutils/template.go:100
msgid "OPTIONS"
msgstr ""
#: internal/db/db.go:137
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "" msgstr ""
#: internal/db/db.go:144 #: internal/db/db.go:140
msgid "" msgid ""
"Database version does not exist. Run alr fix if something isn't working." "Database version does not exist. Run alr fix if something isn't working."
msgstr "" msgstr ""
#: internal/db/db_legacy.go:101
msgid "Error opening database"
msgstr ""
#: internal/dl/dl.go:170 #: internal/dl/dl.go:170
msgid "Source can be updated, updating if required" msgid "Source can be updated, updating if required"
msgstr "" msgstr ""
@ -307,26 +275,18 @@ msgstr ""
msgid "%s %s downloading at %s/s\n" msgid "%s %s downloading at %s/s\n"
msgstr "" msgstr ""
#: internal/logger/log.go:41 #: internal/logger/log.go:47
msgid "ERROR" msgid "ERROR"
msgstr "" msgstr ""
#: internal/utils/cmd.go:95 #: list.go:40
msgid "Error on dropping capabilities"
msgstr ""
#: internal/utils/cmd.go:123
msgid "You need to be root to perform this action"
msgstr ""
#: internal/utils/cmd.go:165
msgid "You need to be a %s member to perform this action"
msgstr ""
#: list.go:41
msgid "List ALR repo packages" msgid "List ALR repo packages"
msgstr "" msgstr ""
#: list.go:91
msgid "Error listing installed packages"
msgstr ""
#: main.go:45 #: main.go:45
msgid "Print the current ALR version and exit" msgid "Print the current ALR version and exit"
msgstr "" msgstr ""
@ -339,76 +299,102 @@ msgstr ""
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "" msgstr ""
#: main.go:145 #: main.go:91
msgid "Show help" msgid ""
"Running ALR as root is forbidden as it may cause catastrophic damage to your "
"system"
msgstr "" msgstr ""
#: main.go:149 #: main.go:125
msgid "Error while running app" msgid "Error while running app"
msgstr "" msgstr ""
#: pkg/build/build.go:395 #: pkg/build/build.go:153
msgid "Failed to prompt user to view build script"
msgstr ""
#: pkg/build/build.go:157
msgid "Building package" msgid "Building package"
msgstr "" msgstr ""
#: pkg/build/build.go:424 #: pkg/build/build.go:228
msgid "The checksums array must be the same length as sources"
msgstr ""
#: pkg/build/build.go:455
msgid "Downloading sources" msgid "Downloading sources"
msgstr "" msgstr ""
#: pkg/build/build.go:549 #: pkg/build/build.go:246
msgid "Installing dependencies" msgid "Building package metadata"
msgstr "" msgstr ""
#: pkg/build/checker.go:43 #: pkg/build/build.go:268
msgid "Compressing package"
msgstr ""
#: pkg/build/build.go:419
msgid "" msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to " "Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?" "build anyway?"
msgstr "" msgstr ""
#: pkg/build/checker.go:67 #: pkg/build/build.go:433
msgid "This package is already installed" msgid "This package is already installed"
msgstr "" msgstr ""
#: pkg/build/find_deps/alt_linux.go:35 #: pkg/build/build.go:457
msgid "Command not found on the system" msgid "Installing build dependencies"
msgstr "" msgstr ""
#: pkg/build/find_deps/alt_linux.go:86 #: pkg/build/build.go:498
msgid "Provided dependency found" msgid "Installing dependencies"
msgstr "" msgstr ""
#: pkg/build/find_deps/alt_linux.go:93 #: pkg/build/build.go:533
msgid "Required dependency found" msgid "The checksums array must be the same length as sources"
msgstr "" msgstr ""
#: pkg/build/find_deps/empty.go:32 #: pkg/build/build.go:584
msgid "AutoProv is not implemented for this package format, so it's skipped" msgid "Would you like to remove the build dependencies?"
msgstr "" msgstr ""
#: pkg/build/find_deps/empty.go:37 #: pkg/build/build.go:647
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/script_executor.go:237
msgid "Building package metadata"
msgstr ""
#: pkg/build/script_executor.go:368
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "" msgstr ""
#: pkg/build/script_executor.go:377 #: pkg/build/build.go:657
msgid "Executing build()" msgid "Executing build()"
msgstr "" msgstr ""
#: pkg/build/script_executor.go:406 pkg/build/script_executor.go:426 #: pkg/build/build.go:687 pkg/build/build.go:707
msgid "Executing %s()" msgid "Executing %s()"
msgstr "" msgstr ""
#: pkg/build/build.go:766
msgid "Error installing native packages"
msgstr ""
#: pkg/build/build.go:790
msgid "Error installing package"
msgstr ""
#: pkg/build/findDeps.go:35
msgid "Command not found on the system"
msgstr ""
#: pkg/build/findDeps.go:82
msgid "Provided dependency found"
msgstr ""
#: pkg/build/findDeps.go:89
msgid "Required dependency found"
msgstr ""
#: pkg/build/utils.go:133
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/utils.go:144
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/repos/pull.go:79 #: pkg/repos/pull.go:79
msgid "Pulling repository" msgid "Pulling repository"
msgstr "" msgstr ""
@ -427,83 +413,87 @@ msgid ""
"updating ALR if something doesn't work." "updating ALR if something doesn't work."
msgstr "" msgstr ""
#: repo.go:39 #: repo.go:41
msgid "Add a new repository" msgid "Add a new repository"
msgstr "" msgstr ""
#: repo.go:46 #: repo.go:48
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "" msgstr ""
#: repo.go:52 #: repo.go:54
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "" msgstr ""
#: repo.go:79 #: repo.go:79 repo.go:136
msgid "Repo \"%s\" already exists" msgid "Error opening config file"
msgstr "" msgstr ""
#: repo.go:90 repo.go:167 #: repo.go:85 repo.go:142
msgid "Error saving config" msgid "Error encoding config"
msgstr "" msgstr ""
#: repo.go:116 #: repo.go:103
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "" msgstr ""
#: repo.go:123 #: repo.go:110
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "" msgstr ""
#: repo.go:156 #: repo.go:128
msgid "Repo \"%s\" does not exist" msgid "Repo does not exist"
msgstr "" msgstr ""
#: repo.go:163 #: repo.go:148
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "" msgstr ""
#: repo.go:186 #: repo.go:154
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "" msgstr ""
#: repo.go:197 #: repo.go:166
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "" msgstr ""
#: search.go:40 #: search.go:36
msgid "Search packages" msgid "Search packages"
msgstr "" msgstr ""
#: search.go:51 #: search.go:42
msgid "Search by name" msgid "Search by name"
msgstr "" msgstr ""
#: search.go:56 #: search.go:47
msgid "Search by description" msgid "Search by description"
msgstr "" msgstr ""
#: search.go:61 #: search.go:52
msgid "Search by repository" msgid "Search by repository"
msgstr "" msgstr ""
#: search.go:66 #: search.go:57
msgid "Search by provides" msgid "Search by provides"
msgstr "" msgstr ""
#: search.go:71 #: search.go:62
msgid "Format output using a Go template" msgid "Format output using a Go template"
msgstr "" msgstr ""
#: search.go:130 #: search.go:94
msgid "Error while executing search" msgid "At least one search parameter is required"
msgstr "" msgstr ""
#: search.go:138 #: search.go:100
msgid "Error db search"
msgstr ""
#: search.go:109
msgid "Error parsing format template" msgid "Error parsing format template"
msgstr "" msgstr ""
#: search.go:153 #: search.go:124
msgid "Error executing template" msgid "Error executing template"
msgstr "" msgstr ""
@ -511,10 +501,10 @@ msgstr ""
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "" msgstr ""
#: upgrade.go:109 upgrade.go:126 #: upgrade.go:90
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "" msgstr ""
#: upgrade.go:129 #: upgrade.go:112
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "" msgstr ""

@ -1,12 +1,12 @@
# #
# x1z53 <x1z53@yandex.ru>, 2025.
# Maxim Slipenko <maks1ms@alt-gnome.ru>, 2025. # Maxim Slipenko <maks1ms@alt-gnome.ru>, 2025.
# x1z53 <x1z53@yandex.ru>, 2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: unnamed project\n" "Project-Id-Version: unnamed project\n"
"PO-Revision-Date: 2025-04-18 07:38+0300\n" "PO-Revision-Date: 2025-01-24 21:20+0300\n"
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n" "Last-Translator: x1z53 <x1z53@yandex.ru>\n"
"Language-Team: Russian\n" "Language-Team: Russian\n"
"Language: ru\n" "Language: ru\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -14,85 +14,90 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Gtranslator 48.0\n" "X-Generator: Gtranslator 47.1\n"
#: build.go:42 #: build.go:44
msgid "Build a local package" msgid "Build a local package"
msgstr "Сборка локального пакета" msgstr "Сборка локального пакета"
#: build.go:48 #: build.go:50
msgid "Path to the build script" msgid "Path to the build script"
msgstr "Путь к скрипту сборки" msgstr "Путь к скрипту сборки"
#: build.go:53 #: build.go:55
msgid "Specify subpackage in script (for multi package script only)" msgid "Specify package in script (for multi package script only)"
msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)" msgstr ""
#: build.go:58 #: build.go:60
msgid "Name of the package to build and its repo (example: default/go-bin)" msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)" msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)"
#: build.go:63 #: build.go:65
msgid "" msgid ""
"Build package from scratch even if there's an already built package available" "Build package from scratch even if there's an already built package available"
msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет" msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет"
#: build.go:73 #: build.go:75
msgid "Error getting working directory" msgid "Error db init"
msgstr "Ошибка при получении рабочего каталога" msgstr ""
#: build.go:118 #: build.go:105
msgid "Cannot get absolute script path"
msgstr "Невозможно получить абсолютный путь к скрипту"
#: build.go:148
msgid "Package not found" msgid "Package not found"
msgstr "Пакет не найден" msgstr ""
#: build.go:161 #: build.go:123
msgid "Nothing to build" msgid "Error pulling repositories"
msgstr "Нечего собирать" msgstr "Ошибка при извлечении репозиториев"
#: build.go:218 #: build.go:131
msgid "Unable to detect a supported package manager on the system"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
#: build.go:137
#, fuzzy
msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: build.go:158
msgid "Error building package" msgid "Error building package"
msgstr "Ошибка при сборке пакета" msgstr "Ошибка при сборке пакета"
#: build.go:225 #: build.go:165
msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога"
#: build.go:174
msgid "Error moving the package" msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета" msgstr "Ошибка при перемещении пакета"
#: build.go:229 #: fix.go:37
msgid "Done"
msgstr "Сделано"
#: fix.go:38
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR" msgstr "Попытка устранить проблемы с ALR"
#: fix.go:59 #: fix.go:44
msgid "Clearing cache directory" msgid "Removing cache directory"
msgstr "Очистка каталога кэша" msgstr "Удаление каталога кэша"
#: fix.go:64 #: fix.go:48
msgid "Unable to open cache directory" msgid "Unable to remove cache directory"
msgstr "Невозможно открыть каталог кэша" msgstr "Не удалось удалить каталог кэша"
#: fix.go:70 #: fix.go:52
msgid "Unable to read cache directory contents"
msgstr "Невозможно прочитать содержимое каталога кэша"
#: fix.go:76
msgid "Unable to remove cache item (%s)"
msgstr "Невозможно удалить элемент кэша (%s)"
#: fix.go:80
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "Восстановление кэша" msgstr "Восстановление кэша"
#: fix.go:84 #: fix.go:56
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "Не удалось создать новый каталог кэша" msgstr "Не удалось создать новый каталог кэша"
#: fix.go:62
msgid "Error pulling repos"
msgstr "Ошибка при извлечении репозиториев"
#: fix.go:66
msgid "Done"
msgstr "Сделано"
#: gen.go:34 #: gen.go:34
msgid "Generate a ALR script from a template" msgid "Generate a ALR script from a template"
msgstr "Генерация скрипта ALR из шаблона" msgstr "Генерация скрипта ALR из шаблона"
@ -101,26 +106,22 @@ msgstr "Генерация скрипта ALR из шаблона"
msgid "Generate a ALR script for a pip module" msgid "Generate a ALR script for a pip module"
msgstr "Генерация скрипта ALR для модуля pip" msgstr "Генерация скрипта ALR для модуля pip"
#: helper.go:42 #: helper.go:41
msgid "List all the available helper commands" msgid "List all the available helper commands"
msgstr "Список всех доступных вспомогательных команды" msgstr "Список всех доступных вспомогательных команды"
#: helper.go:54 #: helper.go:53
msgid "Run a ALR helper command" msgid "Run a ALR helper command"
msgstr "Запустить вспомогательную команду ALR" msgstr "Запустить вспомогательную команду ALR"
#: helper.go:61 #: helper.go:60
msgid "The directory that the install commands will install to" msgid "The directory that the install commands will install to"
msgstr "Каталог, в который будут устанавливать команды установки" msgstr "Каталог, в который будут устанавливать команды установки"
#: helper.go:74 helper.go:75 #: helper.go:73
msgid "No such helper command" msgid "No such helper command"
msgstr "Такой вспомогательной команды нет" msgstr "Такой вспомогательной команды нет"
#: helper.go:85
msgid "Error parsing os-release file"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: info.go:42 #: info.go:42
msgid "Print information about a package" msgid "Print information about a package"
msgstr "Отобразить информацию о пакете" msgstr "Отобразить информацию о пакете"
@ -129,77 +130,57 @@ msgstr "Отобразить информацию о пакете"
msgid "Show all information, not just for the current distro" msgid "Show all information, not just for the current distro"
msgstr "Показывать всю информацию, не только для текущего дистрибутива" msgstr "Показывать всю информацию, не только для текущего дистрибутива"
#: info.go:68 #: info.go:57
msgid "Error getting packages"
msgstr "Ошибка при получении пакетов"
#: info.go:76
msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов"
#: info.go:90
msgid "Command info expected at least 1 argument, got %d"
msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d"
#: info.go:110
msgid "Error finding packages"
msgstr "Ошибка при поиске пакетов"
#: info.go:124
msgid "Can't detect system language"
msgstr "Ошибка при определении языка системы"
#: info.go:141
msgid "Error resolving overrides"
msgstr "Ошибка устранения переорпеделений"
#: info.go:149 info.go:154
msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита"
#: install.go:40
msgid "Install a new package"
msgstr "Установить новый пакет"
#: install.go:56
msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
#: install.go:118
msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: install.go:163
msgid "Remove an installed package"
msgstr "Удалить установленный пакет"
#: install.go:182
msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов"
#: install.go:223
msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
#: install.go:238
msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов"
#: internal/cliutils/app_builder/builder.go:75
msgid "Error loading config"
msgstr "Ошибка при загрузке"
#: internal/cliutils/app_builder/builder.go:96
msgid "Error initialization database" msgid "Error initialization database"
msgstr "Ошибка инициализации базы данных" msgstr "Ошибка инициализации базы данных"
#: internal/cliutils/app_builder/builder.go:135 #: info.go:64
msgid "Error pulling repositories" msgid "Command info expected at least 1 argument, got %d"
msgstr "Ошибка при извлечении репозиториев" msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d"
#: internal/cliutils/app_builder/builder.go:165 #: info.go:78
msgid "Unable to detect a supported package manager on the system" msgid "Error finding packages"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" msgstr "Ошибка при поиске пакетов"
#: info.go:94
msgid "Error parsing os-release file"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: info.go:103
msgid "Error resolving overrides"
msgstr "Ошибка устранения переорпеделений"
#: info.go:112 info.go:118
msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита"
#: install.go:43
msgid "Install a new package"
msgstr "Установить новый пакет"
#: install.go:57
msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
#: install.go:124
msgid "Error getting packages"
msgstr "Ошибка при получении пакетов"
#: install.go:133
msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов"
#: install.go:146
msgid "Remove an installed package"
msgstr "Удалить установленный пакет"
#: install.go:151
msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
#: install.go:163
msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов"
#: internal/cliutils/prompt.go:60 #: internal/cliutils/prompt.go:60
msgid "Would you like to view the build script for %s" msgid "Would you like to view the build script for %s"
@ -225,72 +206,64 @@ msgstr "Выберите, какой пакет использовать для
msgid "Choose which optional package(s) to install" msgid "Choose which optional package(s) to install"
msgstr "Выберите, какой дополнительный пакет(ы) следует установить" msgstr "Выберите, какой дополнительный пакет(ы) следует установить"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:93 #: internal/config/config.go:59
msgid "NAME" msgid "Error opening config file, using defaults"
msgstr "НАЗВАНИЕ" msgstr ""
"Ошибка при открытии конфигурационного файла, используются значения по "
"умолчанию"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:94 #: internal/config/config.go:72
msgid "USAGE" msgid "Error decoding config file, using defaults"
msgstr "ИСПОЛЬЗОВАНИЕ" msgstr ""
"Ошибка при декодировании конфигурационного файла, используются значения по "
"умолчанию"
#: internal/cliutils/template.go:74 #: internal/config/config.go:84
msgid "global options" msgid "Unable to detect user config directory"
msgstr "глобальные опции" msgstr "Не удалось обнаружить каталог конфигурации пользователя"
#: internal/cliutils/template.go:74 #: internal/config/config.go:92
msgid "command" msgid "Unable to create ALR config directory"
msgstr "команда" msgstr "Не удалось создать каталог конфигурации ALR"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:95 #: internal/config/config.go:101
msgid "command options" msgid "Unable to create ALR config file"
msgstr "опции команды" msgstr "Не удалось создать конфигурационный файл ALR"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:96 #: internal/config/config.go:107
msgid "arguments" msgid "Error encoding default configuration"
msgstr "аргументы" msgstr "Ошибка кодирования конфигурации по умолчанию"
#: internal/cliutils/template.go:74 #: internal/config/config.go:116
msgid "VERSION" msgid "Unable to detect cache directory"
msgstr "ВЕРСИЯ" msgstr "Не удалось обнаружить каталог кэша"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:98 #: internal/config/config.go:126
msgid "DESCRIPTION" msgid "Unable to create repo cache directory"
msgstr "ОПИСАНИЕ" msgstr "Не удалось создать каталог кэша репозитория"
#: internal/cliutils/template.go:74 #: internal/config/config.go:132
msgid "AUTHOR" msgid "Unable to create package cache directory"
msgstr "АВТОР" msgstr "Не удалось создать каталог кэша пакетов"
#: internal/cliutils/template.go:74 #: internal/config/lang.go:49
msgid "COMMANDS" msgid "Error parsing system language"
msgstr "КОМАНДЫ" msgstr "Ошибка при парсинге языка системы"
#: internal/cliutils/template.go:74 #: internal/db/db.go:133
msgid "GLOBAL OPTIONS"
msgstr "ГЛОБАЛЬНЫЕ ОПЦИИ"
#: internal/cliutils/template.go:74
msgid "COPYRIGHT"
msgstr "АВТОРСКОЕ ПРАВО"
#: internal/cliutils/template.go:97
msgid "CATEGORY"
msgstr "КАТЕГОРИЯ"
#: internal/cliutils/template.go:99 internal/cliutils/template.go:100
msgid "OPTIONS"
msgstr "ПАРАМЕТРЫ"
#: internal/db/db.go:137
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "Несоответствие версий базы данных; сброс настроек" msgstr "Несоответствие версий базы данных; сброс настроек"
#: internal/db/db.go:144 #: internal/db/db.go:140
msgid "" msgid ""
"Database version does not exist. Run alr fix if something isn't working." "Database version does not exist. Run alr fix if something isn't working."
msgstr "" msgstr ""
"Версия базы данных не существует. Запустите alr fix, если что-то не работает." "Версия базы данных не существует. Запустите alr fix, если что-то не работает."
#: internal/db/db_legacy.go:101
msgid "Error opening database"
msgstr "Ошибка при открытии базы данных"
#: internal/dl/dl.go:170 #: internal/dl/dl.go:170
msgid "Source can be updated, updating if required" msgid "Source can be updated, updating if required"
msgstr "Исходный код можно обновлять, обновляя при необходимости" msgstr "Исходный код можно обновлять, обновляя при необходимости"
@ -309,32 +282,24 @@ msgstr "Скачивание источника"
#: internal/dl/progress_tui.go:100 #: internal/dl/progress_tui.go:100
msgid "%s: done!\n" msgid "%s: done!\n"
msgstr "%s: выполнено!\n" msgstr ""
#: internal/dl/progress_tui.go:104 #: internal/dl/progress_tui.go:104
msgid "%s %s downloading at %s/s\n" msgid "%s %s downloading at %s/s\n"
msgstr "%s %s загружается — %s/с\n" msgstr ""
#: internal/logger/log.go:41 #: internal/logger/log.go:47
msgid "ERROR" msgid "ERROR"
msgstr "ОШИБКА" msgstr "ОШИБКА"
#: internal/utils/cmd.go:95 #: list.go:40
msgid "Error on dropping capabilities"
msgstr "Ошибка при понижении привилегий"
#: internal/utils/cmd.go:123
msgid "You need to be root to perform this action"
msgstr "Вы должны быть root чтобы выполнить это"
#: internal/utils/cmd.go:165
msgid "You need to be a %s member to perform this action"
msgstr "Вы должны быть членом %s чтобы выполнить это"
#: list.go:41
msgid "List ALR repo packages" msgid "List ALR repo packages"
msgstr "Список пакетов репозитория ALR" msgstr "Список пакетов репозитория ALR"
#: list.go:91
msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов"
#: main.go:45 #: main.go:45
msgid "Print the current ALR version and exit" msgid "Print the current ALR version and exit"
msgstr "Показать текущую версию ALR и выйти" msgstr "Показать текущую версию ALR и выйти"
@ -347,31 +312,39 @@ msgstr "Аргументы, которые будут переданы мене
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов" msgstr "Включение интерактивных вопросов и запросов"
#: main.go:145 #: main.go:91
msgid "Show help" msgid ""
msgstr "Показать справку" "Running ALR as root is forbidden as it may cause catastrophic damage to your "
"system"
msgstr ""
"Запуск ALR от имени root запрещён, так как это может привести к "
"катастрофическому повреждению вашей системы"
#: main.go:149 #: main.go:125
msgid "Error while running app" msgid "Error while running app"
msgstr "Ошибка при запуске приложения" msgstr "Ошибка при запуске приложения"
#: pkg/build/build.go:395 #: pkg/build/build.go:153
msgid "Failed to prompt user to view build script"
msgstr "Не удалось предложить пользователю просмотреть скрипт сборки"
#: pkg/build/build.go:157
msgid "Building package" msgid "Building package"
msgstr "Сборка пакета" msgstr "Сборка пакета"
#: pkg/build/build.go:424 #: pkg/build/build.go:228
msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
#: pkg/build/build.go:455
msgid "Downloading sources" msgid "Downloading sources"
msgstr "Скачивание источников" msgstr "Скачивание источников"
#: pkg/build/build.go:549 #: pkg/build/build.go:246
msgid "Installing dependencies" msgid "Building package metadata"
msgstr "Установка зависимостей" msgstr "Сборка метаданных пакета"
#: pkg/build/checker.go:43 #: pkg/build/build.go:268
msgid "Compressing package"
msgstr "Сжатие пакета"
#: pkg/build/build.go:419
msgid "" msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to " "Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?" "build anyway?"
@ -379,48 +352,69 @@ msgstr ""
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все " "Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
"равно хотите выполнить сборку?" "равно хотите выполнить сборку?"
#: pkg/build/checker.go:67 #: pkg/build/build.go:433
msgid "This package is already installed" msgid "This package is already installed"
msgstr "Этот пакет уже установлен" msgstr "Этот пакет уже установлен"
#: pkg/build/find_deps/alt_linux.go:35 #: pkg/build/build.go:457
msgid "Installing build dependencies"
msgstr "Установка зависимостей сборки"
#: pkg/build/build.go:498
msgid "Installing dependencies"
msgstr "Установка зависимостей"
#: pkg/build/build.go:533
msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
#: pkg/build/build.go:584
msgid "Would you like to remove the build dependencies?"
msgstr "Хотели бы вы удалить зависимости сборки?"
#: pkg/build/build.go:647
msgid "Executing prepare()"
msgstr "Исполнение prepare()"
#: pkg/build/build.go:657
msgid "Executing build()"
msgstr "Исполнение build()"
#: pkg/build/build.go:687 pkg/build/build.go:707
#, fuzzy
msgid "Executing %s()"
msgstr "Исполнение files()"
#: pkg/build/build.go:766
msgid "Error installing native packages"
msgstr "Ошибка при установке нативных пакетов"
#: pkg/build/build.go:790
msgid "Error installing package"
msgstr "Ошибка при установке пакета"
#: pkg/build/findDeps.go:35
msgid "Command not found on the system" msgid "Command not found on the system"
msgstr "Команда не найдена в системе" msgstr "Команда не найдена в системе"
#: pkg/build/find_deps/alt_linux.go:86 #: pkg/build/findDeps.go:82
msgid "Provided dependency found" msgid "Provided dependency found"
msgstr "Найденная предоставленная зависимость" msgstr "Найденная предоставленная зависимость"
#: pkg/build/find_deps/alt_linux.go:93 #: pkg/build/findDeps.go:89
msgid "Required dependency found" msgid "Required dependency found"
msgstr "Найдена требуемая зависимость" msgstr "Найдена требуемая зависимость"
#: pkg/build/find_deps/empty.go:32 #: pkg/build/utils.go:133
msgid "AutoProv is not implemented for this package format, so it's skipped" msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr "" msgstr ""
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено" "AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/find_deps/empty.go:37 #: pkg/build/utils.go:144
msgid "AutoReq is not implemented for this package format, so it's skipped" msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr "" msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/script_executor.go:237
msgid "Building package metadata"
msgstr "Сборка метаданных пакета"
#: pkg/build/script_executor.go:368
msgid "Executing prepare()"
msgstr "Выполнение prepare()"
#: pkg/build/script_executor.go:377
msgid "Executing build()"
msgstr "Выполнение build()"
#: pkg/build/script_executor.go:406 pkg/build/script_executor.go:426
msgid "Executing %s()"
msgstr "Выполнение %s()"
#: pkg/repos/pull.go:79 #: pkg/repos/pull.go:79
msgid "Pulling repository" msgid "Pulling repository"
msgstr "Скачивание репозитория" msgstr "Скачивание репозитория"
@ -441,169 +435,105 @@ msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте " "Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает." "обновить ALR, если что-то не работает."
#: repo.go:39 #: repo.go:41
msgid "Add a new repository" msgid "Add a new repository"
msgstr "Добавить новый репозиторий" msgstr "Добавить новый репозиторий"
#: repo.go:46 #: repo.go:48
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "Название нового репозитория" msgstr "Название нового репозитория"
#: repo.go:52 #: repo.go:54
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория" msgstr "URL-адрес нового репозитория"
#: repo.go:79 #: repo.go:79 repo.go:136
msgid "Repo \"%s\" already exists" msgid "Error opening config file"
msgstr "Репозиторий \"%s\" уже существует" msgstr "Ошибка при открытии конфигурационного файла"
#: repo.go:90 repo.go:167 #: repo.go:85 repo.go:142
msgid "Error saving config" msgid "Error encoding config"
msgstr "Ошибка при сохранении конфигурации" msgstr "Ошибка при кодировании конфигурации"
#: repo.go:116 #: repo.go:103
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий" msgstr "Удалить существующий репозиторий"
#: repo.go:123 #: repo.go:110
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён" msgstr "Название репозитория удалён"
#: repo.go:156 #: repo.go:128
msgid "Repo \"%s\" does not exist" msgid "Repo does not exist"
msgstr "Репозитория \"%s\" не существует" msgstr "Репозитория не существует"
#: repo.go:163 #: repo.go:148
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория" msgstr "Ошибка при удалении каталога репозитория"
#: repo.go:186 #: repo.go:154
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных" msgstr "Ошибка при удалении пакетов из базы данных"
#: repo.go:197 #: repo.go:166
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории" msgstr "Скачать все изменённые репозитории"
#: search.go:40 #: search.go:36
msgid "Search packages" msgid "Search packages"
msgstr "Поиск пакетов" msgstr ""
#: search.go:51 #: search.go:42
msgid "Search by name" msgid "Search by name"
msgstr "Искать по имени" msgstr ""
#: search.go:56 #: search.go:47
msgid "Search by description" msgid "Search by description"
msgstr "Искать по описанию" msgstr ""
#: search.go:61 #: search.go:52
#, fuzzy
msgid "Search by repository" msgid "Search by repository"
msgstr "Искать по репозиторию" msgstr "Добавить новый репозиторий"
#: search.go:66 #: search.go:57
msgid "Search by provides" msgid "Search by provides"
msgstr "Иcкать по provides" msgstr ""
#: search.go:71 #: search.go:62
msgid "Format output using a Go template" msgid "Format output using a Go template"
msgstr "Формат выходных данных с использованием шаблона Go" msgstr ""
#: search.go:130 #: search.go:94
msgid "Error while executing search" msgid "At least one search parameter is required"
msgstr "Ошибка при выполнении поиска" msgstr ""
#: search.go:138 #: search.go:100
msgid "Error db search"
msgstr ""
#: search.go:109
#, fuzzy
msgid "Error parsing format template" msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона" msgstr "Ошибка при разборе файла выпуска операционной системы"
#: search.go:153 #: search.go:124
#, fuzzy
msgid "Error executing template" msgid "Error executing template"
msgstr "Ошибка при выполнении шаблона" msgstr "Ошибка при получении пакетов"
#: upgrade.go:47 #: upgrade.go:47
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты" msgstr "Обновить все установленные пакеты"
#: upgrade.go:109 upgrade.go:126 #: upgrade.go:90
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений" msgstr "Ошибка при проверке обновлений"
#: upgrade.go:129 #: upgrade.go:112
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "Здесь нечего делать." msgstr "Здесь нечего делать."
#~ msgid "Error pulling repos"
#~ msgstr "Ошибка при извлечении репозиториев"
#, fuzzy
#~ msgid "Error getting current executable"
#~ msgstr "Ошибка при получении рабочего каталога"
#, fuzzy
#~ msgid "Error mounting"
#~ msgstr "Ошибка при кодировании конфигурации"
#, fuzzy
#~ msgid "Unable to create config directory"
#~ msgstr "Не удалось создать каталог конфигурации ALR"
#~ msgid "Unable to create repo cache directory"
#~ msgstr "Не удалось создать каталог кэша репозитория"
#~ msgid "Unable to create package cache directory"
#~ msgstr "Не удалось создать каталог кэша пакетов"
#~ msgid ""
#~ "Running ALR as root is forbidden as it may cause catastrophic damage to "
#~ "your system"
#~ msgstr ""
#~ "Запуск ALR от имени root запрещён, так как это может привести к "
#~ "катастрофическому повреждению вашей системы"
#~ msgid "Failed to prompt user to view build script"
#~ msgstr "Не удалось предложить пользователю просмотреть скрипт сборки"
#~ msgid "Compressing package"
#~ msgstr "Сжатие пакета"
#~ msgid "Installing build dependencies"
#~ msgstr "Установка зависимостей сборки"
#~ msgid "Would you like to remove the build dependencies?"
#~ msgstr "Хотели бы вы удалить зависимости сборки?"
#~ msgid "Error installing native packages"
#~ msgstr "Ошибка при установке нативных пакетов"
#~ msgid "Error installing package"
#~ msgstr "Ошибка при установке пакета"
#~ msgid "Error opening config file, using defaults"
#~ msgstr ""
#~ "Ошибка при открытии конфигурационного файла, используются значения по "
#~ "умолчанию"
#~ msgid "Error decoding config file, using defaults"
#~ msgstr ""
#~ "Ошибка при декодировании конфигурационного файла, используются значения "
#~ "по умолчанию"
#~ msgid "Unable to detect user config directory"
#~ msgstr "Не удалось обнаружить каталог конфигурации пользователя"
#~ msgid "Unable to create ALR config file"
#~ msgstr "Не удалось создать конфигурационный файл ALR"
#~ msgid "Error encoding default configuration"
#~ msgstr "Ошибка кодирования конфигурации по умолчанию"
#~ msgid "Unable to detect cache directory"
#~ msgstr "Не удалось обнаружить каталог кэша"
#~ msgid "Error opening config file"
#~ msgstr "Ошибка при открытии конфигурационного файла"
#~ msgid "Executing version()" #~ msgid "Executing version()"
#~ msgstr "Исполнение версия()" #~ msgstr "Исполнение версия()"

@ -19,7 +19,12 @@
package types package types
import "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
type BuildOpts struct { type BuildOpts struct {
Script string
Packages []string
Manager manager.Manager
Clean bool Clean bool
Interactive bool Interactive bool
} }
@ -28,9 +33,7 @@ type BuildVarsPre struct {
Version string `sh:"version,required"` Version string `sh:"version,required"`
Release int `sh:"release,required"` Release int `sh:"release,required"`
Epoch uint `sh:"epoch"` Epoch uint `sh:"epoch"`
Summary string `sh:"summary"`
Description string `sh:"desc"` Description string `sh:"desc"`
Group string `sh:"group"`
Homepage string `sh:"homepage"` Homepage string `sh:"homepage"`
Maintainer string `sh:"maintainer"` Maintainer string `sh:"maintainer"`
Architectures []string `sh:"architectures"` Architectures []string `sh:"architectures"`
@ -47,15 +50,31 @@ type BuildVarsPre struct {
Scripts Scripts `sh:"scripts"` Scripts Scripts `sh:"scripts"`
AutoReq []string `sh:"auto_req"` AutoReq []string `sh:"auto_req"`
AutoProv []string `sh:"auto_prov"` AutoProv []string `sh:"auto_prov"`
AutoReqSkipList []string `sh:"auto_req_skiplist"`
AutoProvSkipList []string `sh:"auto_prov_skiplist"`
} }
func (bv *BuildVarsPre) ToBuildVars() BuildVars { func (bv *BuildVarsPre) ToBuildVars() BuildVars {
return BuildVars{ return BuildVars{
Name: "", Name: "",
Base: "", Version: bv.Version,
BuildVarsPre: *bv, Release: bv.Release,
Epoch: bv.Epoch,
Description: bv.Description,
Homepage: bv.Homepage,
Maintainer: bv.Maintainer,
Architectures: bv.Architectures,
Licenses: bv.Licenses,
Provides: bv.Provides,
Conflicts: bv.Conflicts,
Depends: bv.Depends,
BuildDepends: bv.BuildDepends,
OptDepends: bv.OptDepends,
Replaces: bv.Replaces,
Sources: bv.Sources,
Checksums: bv.Checksums,
Backup: bv.Backup,
Scripts: bv.Scripts,
AutoReq: bv.AutoReq,
AutoProv: bv.AutoProv,
} }
} }
@ -63,8 +82,26 @@ func (bv *BuildVarsPre) ToBuildVars() BuildVars {
// to build a package // to build a package
type BuildVars struct { type BuildVars struct {
Name string `sh:"name,required"` Name string `sh:"name,required"`
Base string Version string `sh:"version,required"`
BuildVarsPre Release int `sh:"release,required"`
Epoch uint `sh:"epoch"`
Description string `sh:"desc"`
Homepage string `sh:"homepage"`
Maintainer string `sh:"maintainer"`
Architectures []string `sh:"architectures"`
Licenses []string `sh:"license"`
Provides []string `sh:"provides"`
Conflicts []string `sh:"conflicts"`
Depends []string `sh:"deps"`
BuildDepends []string `sh:"build_deps"`
OptDepends []string `sh:"opt_deps"`
Replaces []string `sh:"replaces"`
Sources []string `sh:"sources"`
Checksums []string `sh:"checksums"`
Backup []string `sh:"backup"`
Scripts Scripts `sh:"scripts"`
AutoReq []string `sh:"auto_req"`
AutoProv []string `sh:"auto_prov"`
} }
type Scripts struct { type Scripts struct {

@ -21,12 +21,12 @@ package types
// Config represents the ALR configuration file // Config represents the ALR configuration file
type Config struct { type Config struct {
RootCmd string `toml:"rootCmd" env:"ALR_ROOT_CMD"` RootCmd string `toml:"rootCmd"`
PagerStyle string `toml:"pagerStyle" env:"ALR_PAGER_STYLE"` PagerStyle string `toml:"pagerStyle"`
IgnorePkgUpdates []string `toml:"ignorePkgUpdates"` IgnorePkgUpdates []string `toml:"ignorePkgUpdates"`
Repos []Repo `toml:"repo"` Repos []Repo `toml:"repo"`
AutoPull bool `toml:"autoPull" env:"ALR_AUTOPULL"` Unsafe Unsafe `toml:"unsafe"`
LogLevel string `toml:"logLevel" env:"ALR_LOG_LEVEL"` AutoPull bool `toml:"autoPull"`
} }
// Repo represents a ALR repo within a configuration file // Repo represents a ALR repo within a configuration file
@ -34,3 +34,7 @@ type Repo struct {
Name string `toml:"name"` Name string `toml:"name"`
URL string `toml:"url"` URL string `toml:"url"`
} }
type Unsafe struct {
AllowRunAsRoot bool `toml:"allowRunAsRoot"`
}

@ -1,186 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 utils
import (
"errors"
"os"
"os/user"
"strconv"
"syscall"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
)
func GetUidGidAlrUserString() (string, string, error) {
u, err := user.Lookup("alr")
if err != nil {
return "", "", err
}
return u.Uid, u.Gid, nil
}
func GetUidGidAlrUser() (int, int, error) {
strUid, strGid, err := GetUidGidAlrUserString()
if err != nil {
return 0, 0, err
}
uid, err := strconv.Atoi(strUid)
if err != nil {
return 0, 0, err
}
gid, err := strconv.Atoi(strGid)
if err != nil {
return 0, 0, err
}
return uid, gid, nil
}
func DropCapsToAlrUser() error {
uid, gid, err := GetUidGidAlrUser()
if err != nil {
return err
}
err = syscall.Setgid(gid)
if err != nil {
return err
}
err = syscall.Setuid(uid)
if err != nil {
return err
}
return EnsureIsAlrUser()
}
func ExitIfCantDropGidToAlr() cli.ExitCoder {
_, gid, err := GetUidGidAlrUser()
if err != nil {
return cliutils.FormatCliExit("cannot get gid alr", err)
}
err = syscall.Setgid(gid)
if err != nil {
return cliutils.FormatCliExit("cannot get setgid alr", err)
}
return nil
}
// ExitIfCantDropCapsToAlrUser attempts to drop capabilities to the already
// running user. Returns a cli.ExitCoder with an error if the operation fails.
// See also [ExitIfCantDropCapsToAlrUserNoPrivs] for a version that also applies
// no-new-privs.
func ExitIfCantDropCapsToAlrUser() cli.ExitCoder {
err := DropCapsToAlrUser()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error on dropping capabilities"), err)
}
return nil
}
func ExitIfCantSetNoNewPrivs() cli.ExitCoder {
if err := NoNewPrivs(); err != nil {
return cliutils.FormatCliExit("error on NoNewPrivs", err)
}
return nil
}
// ExitIfCantDropCapsToAlrUserNoPrivs combines [ExitIfCantDropCapsToAlrUser] with [ExitIfCantSetNoNewPrivs]
func ExitIfCantDropCapsToAlrUserNoPrivs() cli.ExitCoder {
if err := ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
if err := ExitIfCantSetNoNewPrivs(); err != nil {
return err
}
return nil
}
func ExitIfNotRoot() error {
if os.Getuid() != 0 {
return cli.Exit(gotext.Get("You need to be root to perform this action"), 1)
}
return nil
}
func EnsureIsAlrUser() error {
uid, gid, err := GetUidGidAlrUser()
if err != nil {
return err
}
newUid := syscall.Getuid()
if newUid != uid {
return errors.New("new uid don't matches requested")
}
newGid := syscall.Getgid()
if newGid != gid {
return errors.New("new gid don't matches requested")
}
return nil
}
func EnuseIsPrivilegedGroupMember() error {
currentUser, err := user.Current()
if err != nil {
return err
}
group, err := user.LookupGroup(constants.PrivilegedGroup)
if err != nil {
return err
}
groups, err := currentUser.GroupIds()
if err != nil {
return err
}
for _, gid := range groups {
if gid == group.Gid {
return nil
}
}
return cliutils.FormatCliExit(gotext.Get("You need to be a %s member to perform this action", constants.PrivilegedGroup), nil)
}
func EscalateToRootGid() error {
return syscall.Setgid(0)
}
func EscalateToRootUid() error {
return syscall.Setuid(0)
}
func EscalateToRoot() error {
err := EscalateToRootUid()
if err != nil {
return err
}
err = EscalateToRootGid()
if err != nil {
return err
}
return nil
}

@ -1,23 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 utils
import "golang.org/x/sys/unix"
func NoNewPrivs() error {
return unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
}

68
list.go

@ -22,17 +22,16 @@ package main
import ( import (
"fmt" "fmt"
"log/slog" "log/slog"
"os"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func ListCmd() *cli.Command { func ListCmd() *cli.Command {
@ -47,26 +46,23 @@ func ListCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
return err
}
ctx := c.Context ctx := c.Context
cfg := config.New()
deps, err := appbuilder. db := database.New(cfg)
New(ctx). err := db.Init(ctx)
WithConfig().
WithDB().
// autoPull only
WithRepos().
Build()
if err != nil { if err != nil {
return err slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
} }
defer deps.Defer() rs := repos.New(cfg, db)
cfg := deps.Cfg if cfg.AutoPull(ctx) {
db := deps.DB err = rs.Pull(ctx, cfg.Repos(ctx))
if err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1)
}
}
where := "true" where := "true"
args := []any(nil) args := []any(nil)
@ -77,30 +73,23 @@ func ListCmd() *cli.Command {
result, err := db.GetPkgs(ctx, where, args...) result, err := db.GetPkgs(ctx, where, args...)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) slog.Error(gotext.Get("Error getting packages"), "err", err)
os.Exit(1)
} }
defer result.Close() defer result.Close()
installedAlrPackages := map[string]string{} var installed map[string]string
if c.Bool("installed") { if c.Bool("installed") {
mgr := manager.Detect() mgr := manager.Detect()
if mgr == nil { if mgr == nil {
return cli.Exit(gotext.Get("Unable to detect a supported package manager on the system"), 1) slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
} }
installed, err := mgr.ListInstalled(&manager.Opts{}) installed, err = mgr.ListInstalled(&manager.Opts{AsRoot: false})
if err != nil { if err != nil {
slog.Error(gotext.Get("Error listing installed packages"), "err", err) slog.Error(gotext.Get("Error listing installed packages"), "err", err)
return cli.Exit(err, 1) os.Exit(1)
}
for pkgName, version := range installed {
matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName)
if matches != nil {
packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")]
repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")]
installedAlrPackages[fmt.Sprintf("%s/%s", repoName, packageName)] = version
}
} }
} }
@ -108,16 +97,16 @@ func ListCmd() *cli.Command {
var pkg database.Package var pkg database.Package
err := result.StructScan(&pkg) err := result.StructScan(&pkg)
if err != nil { if err != nil {
return cli.Exit(err, 1) return err
} }
if slices.Contains(cfg.IgnorePkgUpdates(), pkg.Name) { if slices.Contains(cfg.IgnorePkgUpdates(ctx), pkg.Name) {
continue continue
} }
version := pkg.Version version := pkg.Version
if c.Bool("installed") { if c.Bool("installed") {
instVersion, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] instVersion, ok := installed[pkg.Name]
if !ok { if !ok {
continue continue
} else { } else {
@ -128,6 +117,11 @@ func ListCmd() *cli.Command {
fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version) fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version)
} }
if err != nil {
slog.Error(gotext.Get("Error iterating over packages"), "err", err)
os.Exit(1)
}
return nil return nil
}, },
} }

62
main.go

@ -31,8 +31,8 @@ import (
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/translations" "gitea.plemya-x.ru/Plemya-x/ALR/internal/translations"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
@ -82,69 +82,45 @@ func GetApp() *cli.App {
HelperCmd(), HelperCmd(),
VersionCmd(), VersionCmd(),
SearchCmd(), SearchCmd(),
// Internal commands
InternalBuildCmd(),
InternalInstallCmd(),
InternalMountCmd(),
}, },
Before: func(c *cli.Context) error { Before: func(c *cli.Context) error {
ctx := c.Context
cmd := c.Args().First()
if cmd != "helper" && !config.Config(ctx).Unsafe.AllowRunAsRoot && os.Geteuid() == 0 {
slog.Error(gotext.Get("Running ALR as root is forbidden as it may cause catastrophic damage to your system"))
os.Exit(1)
}
if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" { if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {
args := strings.Split(trimmed, " ") args := strings.Split(trimmed, " ")
manager.Args = append(manager.Args, args...) manager.Args = append(manager.Args, args...)
} }
return nil return nil
}, },
EnableBashCompletion: true, After: func(ctx *cli.Context) error {
ExitErrHandler: func(cCtx *cli.Context, err error) { return db.Close()
cliutils.HandleExitCoder(err)
}, },
EnableBashCompletion: true,
} }
} }
func setLogLevel(newLevel string) {
level := slog.LevelInfo
switch newLevel {
case "DEBUG":
level = slog.LevelDebug
case "INFO":
level = slog.LevelInfo
case "WARN":
level = slog.LevelWarn
case "ERROR":
level = slog.LevelError
}
logger, ok := slog.Default().Handler().(*logger.Logger)
if !ok {
panic("unexpected")
}
logger.SetLevel(level)
}
func main() { func main() {
logger.SetupDefault()
setLogLevel(os.Getenv("ALR_LOG_LEVEL"))
translations.Setup() translations.Setup()
logger.SetupDefault()
app := GetApp()
ctx := context.Background() ctx := context.Background()
app := GetApp() // Set the root command to the one set in the ALR config
cfg := config.New() manager.DefaultRootCmd = config.Config(ctx).RootCmd
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
setLogLevel(cfg.LogLevel())
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
defer cancel() defer cancel()
// Make the application more internationalized err := app.RunContext(ctx, os.Args)
cli.AppHelpTemplate = cliutils.GetAppCliTemplate()
cli.CommandHelpTemplate = cliutils.GetCommandHelpTemplate()
cli.HelpFlag.(*cli.BoolFlag).Usage = gotext.Get("Show help")
err = app.RunContext(ctx, os.Args)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error while running app"), "err", err) slog.Error(gotext.Get("Error while running app"), "err", err)
} }

File diff suppressed because it is too large Load Diff

@ -144,11 +144,11 @@ func (m *TestManager) IsInstalled(pkg string) (bool, error) {
type TestConfig struct{} type TestConfig struct{}
func (c *TestConfig) PagerStyle() string { func (c *TestConfig) PagerStyle(ctx context.Context) string {
return "native" return "native"
} }
func (c *TestConfig) GetPaths() *config.Paths { func (c *TestConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
CacheDir: "/tmp", CacheDir: "/tmp",
} }
@ -277,7 +277,7 @@ meta_bar() {
fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh") fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh")
assert.NoError(t, err) assert.NoError(t, err)
_, allVars, err := b.scriptExecutor.ExecuteSecondPass(fl) _, allVars, err := b.executeFirstPass(fl)
assert.NoError(t, err) assert.NoError(t, err)
tc.Expected(t, allVars) tc.Expected(t, allVars)

@ -1,69 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"os"
"path/filepath"
"github.com/goreleaser/nfpm/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
type Cache struct {
cfg Config
}
func (c *Cache) CheckForBuiltPackage(
ctx context.Context,
input *BuildInput,
vars *types.BuildVars,
) (string, bool, error) {
filename, err := pkgFileName(input, vars)
if err != nil {
return "", false, err
}
pkgPath := filepath.Join(getBaseDir(c.cfg, vars.Name), filename)
_, err = os.Stat(pkgPath)
if err != nil {
return "", false, nil
}
return pkgPath, true, nil
}
func pkgFileName(
input interface {
OsInfoProvider
PkgFormatProvider
RepositoryProvider
},
vars *types.BuildVars,
) (string, error) {
pkgInfo := getBasePkgInfo(vars, input)
packager, err := nfpm.Get(input.PkgFormat())
if err != nil {
return "", err
}
return packager.ConventionalFileName(pkgInfo), nil
}

@ -1,74 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"log/slog"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
type Checker struct {
mgr manager.Manager
}
func (c *Checker) PerformChecks(
ctx context.Context,
input *BuildInput,
vars *types.BuildVars,
) (bool, error) {
if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры
cont, err := cliutils.YesNoPrompt(
ctx,
gotext.Get("Your system's CPU architecture doesn't match this package. Do you want to build anyway?"),
input.opts.Interactive,
true,
)
if err != nil {
return false, err
}
if !cont {
return false, nil
}
}
installed, err := c.mgr.ListInstalled(nil)
if err != nil {
return false, err
}
filename, err := pkgFileName(input, vars)
if err != nil {
return false, err
}
if instVer, ok := installed[filename]; ok { // Если пакет уже установлен, выводим предупреждение
slog.Warn(gotext.Get("This package is already installed"),
"name", vars.Name,
"version", instVer,
)
}
return true, nil
}

@ -1,71 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"path/filepath"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
type BaseDirProvider interface {
BaseDir() string
}
type SrcDirProvider interface {
SrcDir() string
}
type PkgDirProvider interface {
PkgDir() string
}
type ScriptDirProvider interface {
ScriptDir() string
}
func getDirs(
cfg Config,
scriptPath string,
basePkg string,
) (types.Directories, error) {
pkgsDir := cfg.GetPaths().PkgsDir
scriptPath, err := filepath.Abs(scriptPath)
if err != nil {
return types.Directories{}, err
}
baseDir := filepath.Join(pkgsDir, basePkg)
return types.Directories{
BaseDir: getBaseDir(cfg, basePkg),
SrcDir: getSrcDir(cfg, basePkg),
PkgDir: filepath.Join(baseDir, "pkg"),
ScriptDir: getScriptDir(scriptPath),
}, nil
}
func getBaseDir(cfg Config, basePkg string) string {
return filepath.Join(cfg.GetPaths().PkgsDir, basePkg)
}
func getSrcDir(cfg Config, basePkg string) string {
return filepath.Join(getBaseDir(cfg, basePkg), "src")
}
func getScriptDir(scriptPath string) string {
return filepath.Dir(scriptPath)
}

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package finddeps package build
import ( import (
"bytes" "bytes"
@ -30,7 +30,7 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
) )
func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, envs []string, updateFunc func(string)) error { func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, updateFunc func(string)) error {
if _, err := exec.LookPath(command); err != nil { if _, err := exec.LookPath(command); err != nil {
slog.Info(gotext.Get("Command not found on the system"), "command", command) slog.Info(gotext.Get("Command not found on the system"), "command", command)
return nil return nil
@ -49,8 +49,8 @@ func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs t
return nil return nil
} }
cmd := exec.CommandContext(ctx, command) cmd := exec.Command(command)
cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n") + "\n") cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n"))
cmd.Env = append(cmd.Env, cmd.Env = append(cmd.Env,
"RPM_BUILD_ROOT="+dirs.PkgDir, "RPM_BUILD_ROOT="+dirs.PkgDir,
"RPM_FINDPROV_METHOD=", "RPM_FINDPROV_METHOD=",
@ -58,7 +58,6 @@ func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs t
"RPM_DATADIR=", "RPM_DATADIR=",
"RPM_SUBPACKAGE_NAME=", "RPM_SUBPACKAGE_NAME=",
) )
cmd.Env = append(cmd.Env, envs...)
var out bytes.Buffer var out bytes.Buffer
var stderr bytes.Buffer var stderr bytes.Buffer
cmd.Stdout = &out cmd.Stdout = &out
@ -67,7 +66,6 @@ func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs t
slog.Error(stderr.String()) slog.Error(stderr.String())
return err return err
} }
slog.Debug(stderr.String())
dependencies := strings.Split(strings.TrimSpace(out.String()), "\n") dependencies := strings.Split(strings.TrimSpace(out.String()), "\n")
for _, dep := range dependencies { for _, dep := range dependencies {
@ -79,17 +77,15 @@ func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs t
return nil return nil
} }
type ALTLinuxFindProvReq struct{} func rpmFindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error {
return rpmFindDependencies(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-provides", func(dep string) {
func (o *ALTLinuxFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependenciesALTLinux(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-provides", []string{"RPM_FINDPROV_SKIPLIST=" + strings.Join(skiplist, "\n")}, func(dep string) {
slog.Info(gotext.Get("Provided dependency found"), "dep", dep) slog.Info(gotext.Get("Provided dependency found"), "dep", dep)
pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep) pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep)
}) })
} }
func (o *ALTLinuxFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { func rpmFindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error {
return rpmFindDependenciesALTLinux(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", []string{"RPM_FINDREQ_SKIPLIST=" + strings.Join(skiplist, "\n")}, func(dep string) { return rpmFindDependencies(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", func(dep string) {
slog.Info(gotext.Get("Required dependency found"), "dep", dep) slog.Info(gotext.Get("Required dependency found"), "dep", dep)
pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep) pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep)
}) })

@ -1,118 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 finddeps
import (
"bytes"
"context"
"fmt"
"log/slog"
"os/exec"
"path"
"strings"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
type FedoraFindProvReq struct{}
func rpmFindDependenciesFedora(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, args []string, updateFunc func(string)) error {
if _, err := exec.LookPath(command); err != nil {
slog.Info(gotext.Get("Command not found on the system"), "command", command)
return nil
}
var paths []string
for _, content := range pkgInfo.Contents {
if content.Type != "dir" {
paths = append(paths,
path.Join(dirs.PkgDir, content.Destination),
)
}
}
if len(paths) == 0 {
return nil
}
cmd := exec.CommandContext(ctx, command, args...)
cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n") + "\n")
cmd.Env = append(cmd.Env,
"RPM_BUILD_ROOT="+dirs.PkgDir,
)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
slog.Error(stderr.String())
return err
}
slog.Debug(stderr.String())
dependencies := strings.Split(strings.TrimSpace(out.String()), "\n")
for _, dep := range dependencies {
if dep != "" {
updateFunc(dep)
}
}
return nil
}
func (o *FedoraFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependenciesFedora(
ctx,
pkgInfo,
dirs,
"/usr/lib/rpm/rpmdeps",
[]string{
"--define=_use_internal_dependency_generator 1",
"--provides",
fmt.Sprintf(
"--define=__provides_exclude_from %s\"",
strings.Join(skiplist, "|"),
),
},
func(dep string) {
slog.Info(gotext.Get("Provided dependency found"), "dep", dep)
pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep)
})
}
func (o *FedoraFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependenciesFedora(
ctx,
pkgInfo,
dirs,
"/usr/lib/rpm/rpmdeps",
[]string{
"--define=_use_internal_dependency_generator 1",
"--requires",
fmt.Sprintf(
"--define=__requires_exclude_from %s",
strings.Join(skiplist, "|"),
),
},
func(dep string) {
slog.Info(gotext.Get("Required dependency found"), "dep", dep)
pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep)
})
}

@ -1,58 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 finddeps
import (
"context"
"github.com/goreleaser/nfpm/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
type ProvReqFinder interface {
FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error
FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error
}
type ProvReqService struct {
finder ProvReqFinder
}
func New(info *distro.OSRelease, pkgFormat string) *ProvReqService {
s := &ProvReqService{
finder: &EmptyFindProvReq{},
}
if pkgFormat == "rpm" {
switch info.ID {
case "altlinux":
s.finder = &ALTLinuxFindProvReq{}
case "fedora":
s.finder = &FedoraFindProvReq{}
}
}
return s
}
func (s *ProvReqService) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return s.finder.FindProvides(ctx, pkgInfo, dirs, skiplist)
}
func (s *ProvReqService) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return s.finder.FindRequires(ctx, pkgInfo, dirs, skiplist)
}

@ -1,54 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
func NewInstaller(mgr manager.Manager) *Installer {
return &Installer{
mgr: mgr,
}
}
type Installer struct{ mgr manager.Manager }
func (i *Installer) InstallLocal(paths []string, opts *manager.Opts) error {
return i.mgr.InstallLocal(opts, paths...)
}
func (i *Installer) Install(pkgs []string, opts *manager.Opts) error {
return i.mgr.Install(opts, pkgs...)
}
func (i *Installer) RemoveAlreadyInstalled(pkgs []string) ([]string, error) {
filteredPackages := []string{}
for _, dep := range pkgs {
installed, err := i.mgr.IsInstalled(dep)
if err != nil {
return nil, err
}
if installed {
continue
}
filteredPackages = append(filteredPackages, dep)
}
return filteredPackages, nil
}

@ -1,52 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
func NewMainBuilder(
cfg Config,
mgr manager.Manager,
repos PackageFinder,
scriptExecutor ScriptExecutor,
installerExecutor InstallerExecutor,
) (*Builder, error) {
builder := &Builder{
scriptExecutor: scriptExecutor,
cacheExecutor: &Cache{
cfg,
},
scriptResolver: &ScriptResolver{
cfg,
},
scriptViewerExecutor: &ScriptViewer{
config: cfg,
},
checkerExecutor: &Checker{
mgr,
},
installerExecutor: installerExecutor,
sourceExecutor: &SourceDownloader{
cfg,
},
repos: repos,
}
return builder, nil
}

@ -1,40 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"os"
"os/exec"
"strings"
)
func setCommonCmdEnv(cmd *exec.Cmd) {
cmd.Env = []string{
"HOME=/var/cache/alr",
"LOGNAME=alr",
"USER=alr",
"PATH=/usr/bin:/bin:/usr/local/bin",
}
for _, env := range os.Environ() {
if strings.HasPrefix(env, "LANG=") ||
strings.HasPrefix(env, "LANGUAGE=") ||
strings.HasPrefix(env, "LC_") ||
strings.HasPrefix(env, "ALR_LOG_LEVEL=") {
cmd.Env = append(cmd.Env, env)
}
}
}

@ -1,150 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"fmt"
"log/slog"
"net/rpc"
"os"
"os/exec"
"sync"
"syscall"
"github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
type InstallerPlugin struct {
Impl InstallerExecutor
}
type InstallerRPC struct {
client *rpc.Client
}
type InstallerRPCServer struct {
Impl InstallerExecutor
}
type InstallArgs struct {
PackagesOrPaths []string
Opts *manager.Opts
}
func (r *InstallerRPC) InstallLocal(paths []string, opts *manager.Opts) error {
return r.client.Call("Plugin.InstallLocal", &InstallArgs{
PackagesOrPaths: paths,
Opts: opts,
}, nil)
}
func (s *InstallerRPCServer) InstallLocal(args *InstallArgs, reply *struct{}) error {
return s.Impl.InstallLocal(args.PackagesOrPaths, args.Opts)
}
func (r *InstallerRPC) Install(pkgs []string, opts *manager.Opts) error {
return r.client.Call("Plugin.Install", &InstallArgs{
PackagesOrPaths: pkgs,
Opts: opts,
}, nil)
}
func (s *InstallerRPCServer) Install(args *InstallArgs, reply *struct{}) error {
return s.Impl.Install(args.PackagesOrPaths, args.Opts)
}
func (r *InstallerRPC) RemoveAlreadyInstalled(paths []string) ([]string, error) {
var val []string
err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, &val)
return val, err
}
func (s *InstallerRPCServer) RemoveAlreadyInstalled(pkgs []string, res *[]string) error {
vars, err := s.Impl.RemoveAlreadyInstalled(pkgs)
if err != nil {
return err
}
*res = vars
return nil
}
func (p *InstallerPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &InstallerRPC{client: c}, nil
}
func (p *InstallerPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &InstallerRPCServer{Impl: p.Impl}, nil
}
func GetSafeInstaller() (InstallerExecutor, func(), error) {
var err error
executable, err := os.Executable()
if err != nil {
return nil, nil, err
}
cmd := exec.Command(executable, "_internal-installer")
setCommonCmdEnv(cmd)
slog.Debug("safe installer setup", "uid", syscall.Getuid(), "gid", syscall.Getgid())
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig,
Plugins: pluginMap,
Cmd: cmd,
Logger: logger.GetHCLoggerAdapter(),
SkipHostEnv: true,
UnixSocketConfig: &plugin.UnixSocketConfig{
Group: "alr",
},
SyncStderr: os.Stderr,
})
rpcClient, err := client.Client()
if err != nil {
return nil, nil, err
}
var cleanupOnce sync.Once
cleanup := func() {
cleanupOnce.Do(func() {
client.Kill()
})
}
defer func() {
if err != nil {
slog.Debug("close installer")
cleanup()
}
}()
raw, err := rpcClient.Dispense("installer")
if err != nil {
return nil, nil, err
}
executor, ok := raw.(InstallerExecutor)
if !ok {
err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
return nil, nil, err
}
return executor, cleanup, nil
}

@ -1,272 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"fmt"
"log/slog"
"net/rpc"
"os"
"os/exec"
"sync"
"github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
var HandshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "ALR_PLUGIN",
MagicCookieValue: "-",
}
type ScriptExecutorPlugin struct {
Impl ScriptExecutor
}
type ScriptExecutorRPCServer struct {
Impl ScriptExecutor
}
// =============================
//
// ReadScript
//
func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) {
var resp *ScriptFile
err := s.client.Call("Plugin.ReadScript", scriptPath, &resp)
return resp, err
}
func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile) error {
file, err := s.Impl.ReadScript(context.Background(), scriptPath)
if err != nil {
return err
}
*resp = *file
return nil
}
// =============================
//
// ExecuteFirstPass
//
type ExecuteFirstPassArgs struct {
Input *BuildInput
Sf *ScriptFile
}
type ExecuteFirstPassResp struct {
BasePkg string
VarsOfPackages []*types.BuildVars
}
func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) {
var resp *ExecuteFirstPassResp
err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{
Input: input,
Sf: sf,
}, &resp)
if err != nil {
return "", nil, err
}
return resp.BasePkg, resp.VarsOfPackages, nil
}
func (s *ScriptExecutorRPCServer) ExecuteFirstPass(args *ExecuteFirstPassArgs, resp *ExecuteFirstPassResp) error {
basePkg, varsOfPackages, err := s.Impl.ExecuteFirstPass(context.Background(), args.Input, args.Sf)
if err != nil {
return err
}
*resp = ExecuteFirstPassResp{
BasePkg: basePkg,
VarsOfPackages: varsOfPackages,
}
return nil
}
// =============================
//
// PrepareDirs
//
type PrepareDirsArgs struct {
Input *BuildInput
BasePkg string
}
func (s *ScriptExecutorRPC) PrepareDirs(
ctx context.Context,
input *BuildInput,
basePkg string,
) error {
err := s.client.Call("Plugin.PrepareDirs", &PrepareDirsArgs{
Input: input,
BasePkg: basePkg,
}, nil)
if err != nil {
return err
}
return err
}
func (s *ScriptExecutorRPCServer) PrepareDirs(args *PrepareDirsArgs, reply *struct{}) error {
err := s.Impl.PrepareDirs(
context.Background(),
args.Input,
args.BasePkg,
)
if err != nil {
return err
}
return err
}
// =============================
//
// ExecuteSecondPass
//
type ExecuteSecondPassArgs struct {
Input *BuildInput
Sf *ScriptFile
VarsOfPackages []*types.BuildVars
RepoDeps []string
BuiltNames []string
BasePkg string
}
func (s *ScriptExecutorRPC) ExecuteSecondPass(
ctx context.Context,
input *BuildInput,
sf *ScriptFile,
varsOfPackages []*types.BuildVars,
repoDeps []string,
builtNames []string,
basePkg string,
) (*SecondPassResult, error) {
var resp *SecondPassResult
err := s.client.Call("Plugin.ExecuteSecondPass", &ExecuteSecondPassArgs{
Input: input,
Sf: sf,
VarsOfPackages: varsOfPackages,
RepoDeps: repoDeps,
BuiltNames: builtNames,
BasePkg: basePkg,
}, &resp)
if err != nil {
return nil, err
}
return resp, nil
}
func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *SecondPassResult) error {
res, err := s.Impl.ExecuteSecondPass(
context.Background(),
args.Input,
args.Sf,
args.VarsOfPackages,
args.RepoDeps,
args.BuiltNames,
args.BasePkg,
)
if err != nil {
return err
}
*resp = *res
return err
}
//
// ============================
//
func (p *ScriptExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &ScriptExecutorRPCServer{Impl: p.Impl}, nil
}
func (p *ScriptExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &ScriptExecutorRPC{client: c}, nil
}
type ScriptExecutorRPC struct {
client *rpc.Client
}
var pluginMap = map[string]plugin.Plugin{
"script-executor": &ScriptExecutorPlugin{},
"installer": &InstallerPlugin{},
}
func GetSafeScriptExecutor() (ScriptExecutor, func(), error) {
var err error
executable, err := os.Executable()
if err != nil {
return nil, nil, err
}
cmd := exec.Command(executable, "_internal-safe-script-executor")
setCommonCmdEnv(cmd)
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig,
Plugins: pluginMap,
Cmd: cmd,
Logger: logger.GetHCLoggerAdapter(),
SkipHostEnv: true,
UnixSocketConfig: &plugin.UnixSocketConfig{
Group: "alr",
},
})
rpcClient, err := client.Client()
if err != nil {
return nil, nil, err
}
var cleanupOnce sync.Once
cleanup := func() {
cleanupOnce.Do(func() {
client.Kill()
})
}
defer func() {
if err != nil {
slog.Debug("close script-executor")
cleanup()
}
}()
raw, err := rpcClient.Dispense("script-executor")
if err != nil {
return nil, nil, err
}
executor, ok := raw.(ScriptExecutor)
if !ok {
err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
return nil, nil, err
}
return executor, cleanup, nil
}

@ -1,447 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"bytes"
"context"
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"time"
"github.com/google/shlex"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
finddeps "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build/find_deps"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
type LocalScriptExecutor struct {
cfg Config
}
func NewLocalScriptExecutor(cfg Config) *LocalScriptExecutor {
return &LocalScriptExecutor{
cfg,
}
}
func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) {
fl, err := readScript(scriptPath)
if err != nil {
return nil, err
}
return &ScriptFile{
Path: scriptPath,
File: fl,
}, nil
}
func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) {
varsOfPackages := []*types.BuildVars{}
scriptDir := filepath.Dir(sf.Path)
env := createBuildEnvVars(input.info, types.Directories{ScriptDir: scriptDir})
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод
interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение
interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)), // Ограничиваем чтение директорий
interp.StatHandler(handlers.RestrictedStat(scriptDir)), // Ограничиваем доступ к статистике файлов
interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов
interp.Dir(scriptDir),
)
if err != nil {
return "", nil, err
}
err = runner.Run(ctx, sf.File) // Запускаем скрипт
if err != nil {
return "", nil, err
}
dec := decoder.New(input.info, runner) // Создаём новый декодер
type packages struct {
BasePkgName string `sh:"basepkg_name"`
Names []string `sh:"name"`
}
var pkgs packages
err = dec.DecodeVars(&pkgs)
if err != nil {
return "", nil, err
}
if len(pkgs.Names) == 0 {
return "", nil, errors.New("package name is missing")
}
var vars types.BuildVars
if len(pkgs.Names) == 1 {
err = dec.DecodeVars(&vars) // Декодируем переменные
if err != nil {
return "", nil, err
}
varsOfPackages = append(varsOfPackages, &vars)
return vars.Name, varsOfPackages, nil
}
if len(input.packages) == 0 {
return "", nil, errors.New("script has multiple packages but package is not specified")
}
for _, pkgName := range input.packages {
var preVars types.BuildVarsPre
funcName := fmt.Sprintf("meta_%s", pkgName)
meta, ok := dec.GetFuncWithSubshell(funcName)
if !ok {
return "", nil, errors.New("func is missing")
}
r, err := meta(ctx)
if err != nil {
return "", nil, err
}
d := decoder.New(&distro.OSRelease{}, r)
err = d.DecodeVars(&preVars)
if err != nil {
return "", nil, err
}
vars := preVars.ToBuildVars()
vars.Name = pkgName
vars.Base = pkgs.BasePkgName
varsOfPackages = append(varsOfPackages, &vars)
}
return pkgs.BasePkgName, varsOfPackages, nil
}
type SecondPassResult struct {
BuiltPaths []string
BuiltNames []string
}
func (e *LocalScriptExecutor) PrepareDirs(
ctx context.Context,
input *BuildInput,
basePkg string,
) error {
dirs, err := getDirs(
e.cfg,
input.script,
basePkg,
)
if err != nil {
return err
}
err = prepareDirs(dirs)
if err != nil {
return err
}
return nil
}
func (e *LocalScriptExecutor) ExecuteSecondPass(
ctx context.Context,
input *BuildInput,
sf *ScriptFile,
varsOfPackages []*types.BuildVars,
repoDeps []string,
builtNames []string,
basePkg string,
) (*SecondPassResult, error) {
dirs, err := getDirs(e.cfg, sf.Path, basePkg)
if err != nil {
return nil, err
}
env := createBuildEnvVars(input.info, dirs)
fakeroot := handlers.FakerootExecHandler(2 * time.Second)
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод
interp.ExecHandlers(func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
return helpers.Helpers.ExecHandler(fakeroot)
}), // Обрабатываем выполнение через fakeroot
)
if err != nil {
return nil, err
}
err = runner.Run(ctx, sf.File)
if err != nil {
return nil, err
}
dec := decoder.New(input.info, runner)
var builtPaths []string
err = e.ExecuteFunctions(ctx, dirs, dec)
if err != nil {
return nil, err
}
for _, vars := range varsOfPackages {
packageName := ""
if vars.Base != "" {
packageName = vars.Name
}
pkgFormat := input.pkgFormat
funcOut, err := e.ExecutePackageFunctions(
ctx,
dec,
dirs,
packageName,
)
if err != nil {
return nil, err
}
slog.Info(gotext.Get("Building package metadata"), "name", basePkg)
pkgInfo, err := buildPkgMetadata(
ctx,
input,
vars,
dirs,
append(
repoDeps,
builtNames...,
),
funcOut.Contents,
)
if err != nil {
return nil, err
}
packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета
if err != nil {
return nil, err
}
pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета
pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету
pkgFile, err := os.Create(pkgPath)
if err != nil {
return nil, err
}
err = packager.Package(pkgInfo, pkgFile)
if err != nil {
return nil, err
}
builtPaths = append(builtPaths, pkgPath)
builtNames = append(builtNames, vars.Name)
}
return &SecondPassResult{
BuiltPaths: builtPaths,
BuiltNames: builtNames,
}, nil
}
func buildPkgMetadata(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
RepositoryProvider
},
vars *types.BuildVars,
dirs types.Directories,
deps []string,
preferedContents *[]string,
) (*nfpm.Info, error) {
pkgInfo := getBasePkgInfo(vars, input)
pkgInfo.Description = vars.Description
pkgInfo.Platform = "linux"
pkgInfo.Homepage = vars.Homepage
pkgInfo.License = strings.Join(vars.Licenses, ", ")
pkgInfo.Maintainer = vars.Maintainer
pkgInfo.Overridables = nfpm.Overridables{
Conflicts: append(vars.Conflicts, vars.Name),
Replaces: vars.Replaces,
Provides: append(vars.Provides, vars.Name),
Depends: deps,
}
pkgInfo.Section = vars.Group
pkgFormat := input.PkgFormat()
info := input.OSRelease()
if pkgFormat == "apk" {
// Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы
pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool {
return s == pkgInfo.Name
})
}
if pkgFormat == "rpm" {
pkgInfo.RPM.Group = vars.Group
if vars.Summary != "" {
pkgInfo.RPM.Summary = vars.Summary
} else {
lines := strings.SplitN(vars.Description, "\n", 2)
pkgInfo.RPM.Summary = lines[0]
}
}
if vars.Epoch != 0 {
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
}
setScripts(vars, pkgInfo, dirs.ScriptDir)
if slices.Contains(vars.Architectures, "all") {
pkgInfo.Arch = "all"
}
contents, err := buildContents(vars, dirs, preferedContents)
if err != nil {
return nil, err
}
pkgInfo.Overridables.Contents = contents
if len(vars.AutoProv) == 1 && decoder.IsTruthy(vars.AutoProv[0]) {
f := finddeps.New(info, pkgFormat)
err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList)
if err != nil {
return nil, err
}
}
if len(vars.AutoReq) == 1 && decoder.IsTruthy(vars.AutoReq[0]) {
f := finddeps.New(info, pkgFormat)
err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList)
if err != nil {
return nil, err
}
}
return pkgInfo, nil
}
func (e *LocalScriptExecutor) ExecuteFunctions(ctx context.Context, dirs types.Directories, dec *decoder.Decoder) error {
prepare, ok := dec.GetFunc("prepare")
if ok {
slog.Info(gotext.Get("Executing prepare()"))
err := prepare(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return err
}
}
build, ok := dec.GetFunc("build")
if ok {
slog.Info(gotext.Get("Executing build()"))
err := build(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return err
}
}
return nil
}
func (e *LocalScriptExecutor) ExecutePackageFunctions(
ctx context.Context,
dec *decoder.Decoder,
dirs types.Directories,
packageName string,
) (*FunctionsOutput, error) {
output := &FunctionsOutput{}
var packageFuncName string
var filesFuncName string
if packageName == "" {
packageFuncName = "package"
filesFuncName = "files"
} else {
packageFuncName = fmt.Sprintf("package_%s", packageName)
filesFuncName = fmt.Sprintf("files_%s", packageName)
}
packageFn, ok := dec.GetFunc(packageFuncName)
if ok {
slog.Info(gotext.Get("Executing %s()", packageFuncName))
err := packageFn(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return nil, err
}
}
files, ok := dec.GetFuncP(filesFuncName, func(ctx context.Context, s *interp.Runner) error {
// It should be done via interp.RunnerOption,
// but due to the issues below, it cannot be done.
// - https://github.com/mvdan/sh/issues/962
// - https://github.com/mvdan/sh/issues/1125
script, err := syntax.NewParser().Parse(strings.NewReader("cd $pkgdir && shopt -s globstar"), "")
if err != nil {
return err
}
return s.Run(ctx, script)
})
if ok {
slog.Info(gotext.Get("Executing %s()", filesFuncName))
buf := &bytes.Buffer{}
err := files(
ctx,
interp.Dir(dirs.PkgDir),
interp.StdIO(os.Stdin, buf, os.Stderr),
)
if err != nil {
return nil, err
}
contents, err := shlex.Split(buf.String())
if err != nil {
return nil, err
}
output.Contents = &contents
}
return output, nil
}

@ -1,53 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"path/filepath"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
)
type ScriptResolver struct {
cfg Config
}
type ScriptInfo struct {
Script string
Repository string
}
func (s *ScriptResolver) ResolveScript(
ctx context.Context,
pkg *db.Package,
) *ScriptInfo {
var repository, script string
repodir := s.cfg.GetPaths().RepoDir
repository = pkg.Repository
if pkg.BasePkgName != "" {
script = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh")
} else {
script = filepath.Join(repodir, repository, pkg.Name, "alr.sh")
}
return &ScriptInfo{
Repository: repository,
Script: script,
}
}

@ -1,46 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
)
type ScriptViewerConfig interface {
PagerStyle() string
}
type ScriptViewer struct {
config ScriptViewerConfig
}
func (s *ScriptViewer) ViewScript(
ctx context.Context,
input *BuildInput,
sf *ScriptFile,
basePkg string,
) error {
return cliutils.PromptViewScript(
ctx,
sf.Path,
basePkg,
s.config.PagerStyle(),
input.opts.Interactive,
)
}

@ -1,86 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"encoding/hex"
"fmt"
"os"
"strings"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
)
type SourceDownloader struct {
cfg Config
}
func NewSourceDownloader(cfg Config) *SourceDownloader {
return &SourceDownloader{
cfg,
}
}
func (s *SourceDownloader) DownloadSources(
ctx context.Context,
input *BuildInput,
basePkg string,
si SourcesInput,
) error {
for i, src := range si.Sources {
opts := dl.Options{
Name: fmt.Sprintf("[%d]", i),
URL: src,
Destination: getSrcDir(s.cfg, basePkg),
Progress: os.Stderr,
LocalDir: getScriptDir(input.script),
}
if !strings.EqualFold(si.Checksums[i], "SKIP") {
// Если контрольная сумма содержит двоеточие, используйте часть до двоеточия
// как алгоритм, а часть после как фактическую контрольную сумму.
// В противном случае используйте sha256 по умолчанию с целой строкой как контрольной суммой.
algo, hashData, ok := strings.Cut(si.Checksums[i], ":")
if ok {
checksum, err := hex.DecodeString(hashData)
if err != nil {
return err
}
opts.Hash = checksum
opts.HashAlgorithm = algo
} else {
checksum, err := hex.DecodeString(si.Checksums[i])
if err != nil {
return err
}
opts.Hash = checksum
}
}
opts.DlCache = dlcache.New(s.cfg)
err := dl.Download(ctx, opts)
if err != nil {
return err
}
}
return nil
}

@ -17,11 +17,11 @@
package build package build
import ( import (
"fmt" "context"
"io" "io"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"slices" "slices"
"strconv" "strconv"
@ -33,13 +33,16 @@ import (
_ "github.com/goreleaser/nfpm/v2/arch" _ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb" _ "github.com/goreleaser/nfpm/v2/deb"
_ "github.com/goreleaser/nfpm/v2/rpm" _ "github.com/goreleaser/nfpm/v2/rpm"
"github.com/leonelquinteros/gotext"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
"github.com/goreleaser/nfpm/v2" "github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files" "github.com/goreleaser/nfpm/v2/files"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" "gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
@ -74,6 +77,77 @@ func prepareDirs(dirs types.Directories) error {
return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов
} }
// Функция buildPkgMetadata создает метаданные для пакета, который будет собран.
func buildPkgMetadata(
ctx context.Context,
vars *types.BuildVars,
dirs types.Directories,
pkgFormat string,
info *distro.OSRelease,
deps []string,
preferedContents *[]string,
) (*nfpm.Info, error) {
pkgInfo := getBasePkgInfo(vars, info)
pkgInfo.Description = vars.Description
pkgInfo.Platform = "linux"
pkgInfo.Homepage = vars.Homepage
pkgInfo.License = strings.Join(vars.Licenses, ", ")
pkgInfo.Maintainer = vars.Maintainer
pkgInfo.Overridables = nfpm.Overridables{
Conflicts: vars.Conflicts,
Replaces: vars.Replaces,
Provides: vars.Provides,
Depends: deps,
}
if pkgFormat == "apk" {
// Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы
pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool {
return s == pkgInfo.Name
})
}
if vars.Epoch != 0 {
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
}
setScripts(vars, pkgInfo, dirs.ScriptDir)
if slices.Contains(vars.Architectures, "all") {
pkgInfo.Arch = "all"
}
contents, err := buildContents(vars, dirs, preferedContents)
if err != nil {
return nil, err
}
pkgInfo.Overridables.Contents = contents
if len(vars.AutoProv) == 1 && decoder.IsTruthy(vars.AutoProv[0]) {
if pkgFormat == "rpm" {
err = rpmFindProvides(ctx, pkgInfo, dirs)
if err != nil {
return nil, err
}
} else {
slog.Info(gotext.Get("AutoProv is not implemented for this package format, so it's skipped"))
}
}
if len(vars.AutoReq) == 1 && decoder.IsTruthy(vars.AutoReq[0]) {
if pkgFormat == "rpm" {
err = rpmFindRequires(ctx, pkgInfo, dirs)
if err != nil {
return nil, err
}
} else {
slog.Info(gotext.Get("AutoReq is not implemented for this package format, so it's skipped"))
}
}
return pkgInfo, nil
}
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы, // Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
// которые будут включены в конечный пакет. // которые будут включены в конечный пакет.
func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) { func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
@ -170,25 +244,56 @@ func buildContents(vars *types.BuildVars, dirs types.Directories, preferedConten
return contents, nil return contents, nil
} }
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`) // Функция checkForBuiltPackage пытается обнаружить ранее собранный пакет и вернуть его путь
// и true, если нашла. Если нет, возвратит "", false, nil.
func checkForBuiltPackage(
mgr manager.Manager,
vars *types.BuildVars,
pkgFormat,
baseDir string,
info *distro.OSRelease,
) (string, bool, error) {
filename, err := pkgFileName(vars, pkgFormat, info)
if err != nil {
return "", false, err
}
func getBasePkgInfo(vars *types.BuildVars, input interface { pkgPath := filepath.Join(baseDir, filename)
RepositoryProvider
OsInfoProvider _, err = os.Stat(pkgPath)
}, if err != nil {
) *nfpm.Info { return "", false, nil
}
return pkgPath, true, nil
}
func getBasePkgInfo(vars *types.BuildVars, info *distro.OSRelease) *nfpm.Info {
return &nfpm.Info{ return &nfpm.Info{
Name: fmt.Sprintf("%s+alr-%s", vars.Name, input.Repository()), Name: vars.Name,
Arch: cpu.Arch(), Arch: cpu.Arch(),
Version: vars.Version, Version: vars.Version,
Release: overrides.ReleasePlatformSpecific(vars.Release, input.OSRelease()), Release: overrides.ReleasePlatformSpecific(vars.Release, info),
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10), Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
} }
} }
// pkgFileName returns the filename of the package if it were to be built.
// This is used to check if the package has already been built.
func pkgFileName(vars *types.BuildVars, pkgFormat string, info *distro.OSRelease) (string, error) {
pkgInfo := getBasePkgInfo(vars, info)
packager, err := nfpm.Get(pkgFormat)
if err != nil {
return "", err
}
return packager.ConventionalFileName(pkgInfo), nil
}
// Функция getPkgFormat возвращает формат пакета из менеджера пакетов, // Функция getPkgFormat возвращает формат пакета из менеджера пакетов,
// или ALR_PKG_FORMAT, если он установлен. // или ALR_PKG_FORMAT, если он установлен.
func GetPkgFormat(mgr manager.Manager) string { func getPkgFormat(mgr manager.Manager) string {
pkgFormat := mgr.Format() pkgFormat := mgr.Format()
if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok { if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok {
pkgFormat = format pkgFormat = format
@ -275,9 +380,25 @@ func setVersion(ctx context.Context, r *interp.Runner, to string) error {
return r.Run(ctx, fl) return r.Run(ctx, fl)
} }
*/ */
// Returns not installed dependencies
func removeAlreadyInstalled(opts types.BuildOpts, dependencies []string) ([]string, error) {
filteredPackages := []string{}
for _, dep := range dependencies {
installed, err := opts.Manager.IsInstalled(dep)
if err != nil {
return nil, err
}
if installed {
continue
}
filteredPackages = append(filteredPackages, dep)
}
return filteredPackages, nil
}
// Функция packageNames возвращает имена всех предоставленных пакетов. // Функция packageNames возвращает имена всех предоставленных пакетов.
/*
func packageNames(pkgs []db.Package) []string { func packageNames(pkgs []db.Package) []string {
names := make([]string, len(pkgs)) names := make([]string, len(pkgs))
for i, p := range pkgs { for i, p := range pkgs {
@ -285,7 +406,6 @@ func packageNames(pkgs []db.Package) []string {
} }
return names return names
} }
*/
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза. // Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
func removeDuplicates(slice []string) []string { func removeDuplicates(slice []string) []string {
@ -301,24 +421,3 @@ func removeDuplicates(slice []string) []string {
return result return result
} }
func removeDuplicatesSources(sources, checksums []string) ([]string, []string) {
seen := map[string]string{}
keys := make([]string, 0)
for i, s := range sources {
if val, ok := seen[s]; !ok || strings.EqualFold(val, "SKIP") {
if !ok {
keys = append(keys, s)
}
seen[s] = checksums[i]
}
}
newSources := make([]string, len(keys))
newChecksums := make([]string, len(keys))
for i, k := range keys {
newSources[i] = k
newChecksums[i] = seen[k]
}
return newSources, newChecksums
}

@ -1,47 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRemoveDuplicatesSources(t *testing.T) {
type testCase struct {
Name string
Sources []string
Checksums []string
NewSources []string
NewChecksums []string
}
for _, tc := range []testCase{{
Name: "prefer non-skip values",
Sources: []string{"a", "b", "c", "a"},
Checksums: []string{"skip", "skip", "skip", "1"},
NewSources: []string{"a", "b", "c"},
NewChecksums: []string{"1", "skip", "skip"},
}} {
t.Run(tc.Name, func(t *testing.T) {
s, c := removeDuplicatesSources(tc.Sources, tc.Checksums)
assert.Equal(t, s, tc.NewSources)
assert.Equal(t, c, tc.NewChecksums)
})
}
}

@ -79,10 +79,9 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
runner, err := interp.New( runner, err := interp.New(
interp.OpenHandler(handlers.NopOpen), interp.OpenHandler(handlers.NopOpen),
interp.ExecHandler(handlers.NopExec), interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler2(handlers.NopReadDir), interp.ReadDirHandler(handlers.NopReadDir),
interp.StatHandler(handlers.NopStat), interp.StatHandler(handlers.NopStat),
interp.Env(expand.ListEnviron()), interp.Env(expand.ListEnviron()),
interp.Dir("/"),
) )
if err != nil { if err != nil {
return nil, err return nil, err

@ -32,19 +32,19 @@ deps=("python3")
deps_arch=("python") deps_arch=("python")
deps_alpine=("python3") deps_alpine=("python3")
build_deps=("python3" "python3-pip") build_deps=("python3" "python3-setuptools")
build_deps_arch=("python" "python-pip") build_deps_arch=("python" "python-setuptools")
build_deps_alpine=("python3" "py3-pip") build_deps_alpine=("python3" "py3-setuptools")
sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}") sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}")
checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}') checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}')
build() { build() {
cd "$srcdir/{{.Info.Name}}-${version}" cd "$srcdir/{{.Info.Name}}-${version}"
python -m build --wheel --no-isolation python3 setup.py build
} }
package() { package() {
cd "$srcdir/{{.Info.Name}}-${version}" cd "$srcdir/{{.Info.Name}}-${version}"
pip install --root="${pkgdir}/" . --no-deps --ignore-installed --disable-pip-version-check python3 setup.py install --root="${pkgdir}/" --optimize=1 || return 1
} }

@ -28,15 +28,7 @@ import (
// APK represents the APK package manager // APK represents the APK package manager
type APK struct { type APK struct {
CommonPackageManager rootCmd string
}
func NewAPK() *APK {
return &APK{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-i",
},
}
} }
func (*APK) Exists() bool { func (*APK) Exists() bool {
@ -52,6 +44,10 @@ func (*APK) Format() string {
return "apk" return "apk"
} }
func (a *APK) SetRootCmd(s string) {
a.rootCmd = s
}
func (a *APK) Sync(opts *Opts) error { func (a *APK) Sync(opts *Opts) error {
opts = ensureOpts(opts) opts = ensureOpts(opts)
cmd := a.getCmd(opts, "apk", "update") cmd := a.getCmd(opts, "apk", "update")
@ -167,3 +163,20 @@ func (a *APK) IsInstalled(pkg string) (bool, error) {
} }
return true, nil return true, nil
} }
func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)
}
if !opts.NoConfirm {
cmd.Args = append(cmd.Args, "-i")
}
return cmd
}

@ -28,15 +28,7 @@ import (
// APT represents the APT package manager // APT represents the APT package manager
type APT struct { type APT struct {
CommonPackageManager rootCmd string
}
func NewAPT() *APT {
return &APT{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-y",
},
}
} }
func (*APT) Exists() bool { func (*APT) Exists() bool {
@ -52,6 +44,10 @@ func (*APT) Format() string {
return "deb" return "deb"
} }
func (a *APT) SetRootCmd(s string) {
a.rootCmd = s
}
func (a *APT) Sync(opts *Opts) error { func (a *APT) Sync(opts *Opts) error {
opts = ensureOpts(opts) opts = ensureOpts(opts)
cmd := a.getCmd(opts, "apt", "update") cmd := a.getCmd(opts, "apt", "update")
@ -153,3 +149,20 @@ func (a *APT) IsInstalled(pkg string) (bool, error) {
} }
return true, nil return true, nil
} }
func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)
}
if opts.NoConfirm {
cmd.Args = append(cmd.Args, "-y")
}
return cmd
}

@ -24,24 +24,8 @@ import (
// APTRpm represents the APT-RPM package manager // APTRpm represents the APT-RPM package manager
type APTRpm struct { type APTRpm struct {
CommonPackageManager
CommonRPM CommonRPM
} rootCmd string
func NewAPTRpm() *APTRpm {
return &APTRpm{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-y",
},
}
}
func (*APTRpm) Name() string {
return "apt-rpm"
}
func (*APTRpm) Format() string {
return "rpm"
} }
func (*APTRpm) Exists() bool { func (*APTRpm) Exists() bool {
@ -54,6 +38,18 @@ func (*APTRpm) Exists() bool {
return strings.Contains(string(output), "RPM") return strings.Contains(string(output), "RPM")
} }
func (*APTRpm) Name() string {
return "apt-rpm"
}
func (*APTRpm) Format() string {
return "rpm"
}
func (a *APTRpm) SetRootCmd(s string) {
a.rootCmd = s
}
func (a *APTRpm) Sync(opts *Opts) error { func (a *APTRpm) Sync(opts *Opts) error {
opts = ensureOpts(opts) opts = ensureOpts(opts)
cmd := a.getCmd(opts, "apt-get", "update") cmd := a.getCmd(opts, "apt-get", "update")
@ -70,7 +66,6 @@ func (a *APTRpm) Install(opts *Opts, pkgs ...string) error {
cmd := a.getCmd(opts, "apt-get", "install") cmd := a.getCmd(opts, "apt-get", "install")
cmd.Args = append(cmd.Args, pkgs...) cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd) setCmdEnv(cmd)
cmd.Stdout = cmd.Stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return fmt.Errorf("apt-get: install: %w", err) return fmt.Errorf("apt-get: install: %w", err)
@ -110,3 +105,20 @@ func (a *APTRpm) UpgradeAll(opts *Opts) error {
} }
return nil return nil
} }
func (a *APTRpm) 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...)
}
if opts.NoConfirm {
cmd.Args = append(cmd.Args, "-y")
}
return cmd
}

@ -1,35 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 manager
import "os/exec"
type CommonPackageManager struct {
noConfirmArg string
}
func (m *CommonPackageManager) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
cmd := exec.Command(mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
if opts.NoConfirm {
cmd.Args = append(cmd.Args, m.noConfirmArg)
}
return cmd
}

@ -1,21 +1,20 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов. * ALR - Any Linux Repository
// * ALR - Любой Linux Репозиторий
// ALR - Any Linux Repository * Copyright (C) 2024 Евгений Храмов
// Copyright (C) 2025 Евгений Храмов *
// * This program является свободным: вы можете распространять его и/или изменять
// This program is free software: you can redistribute it and/or modify * на условиях GNU General Public License, опубликованной Free Software Foundation,
// it under the terms of the GNU General Public License as published by * либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
// 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 * Подробности см. в GNU General Public License.
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
// GNU General Public License for more details. * Вы должны были получить копию GNU General Public License
// * вместе с этой программой. Если нет, см. <http://www.gnu.org/licenses/>.
// 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 manager package manager
@ -24,32 +23,33 @@ import (
"os/exec" "os/exec"
) )
// DNF представляет менеджер пакетов DNF
type DNF struct { type DNF struct {
CommonPackageManager
CommonRPM CommonRPM
rootCmd string // rootCmd хранит команду, используемую для выполнения команд с правами root
} }
func NewDNF() *DNF { // Exists проверяет, доступен ли DNF в системе, возвращает true если да
return &DNF{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-y",
},
}
}
func (*DNF) Exists() bool { func (*DNF) Exists() bool {
_, err := exec.LookPath("dnf") _, err := exec.LookPath("dnf")
return err == nil return err == nil
} }
// Name возвращает имя менеджера пакетов, в данном случае "dnf"
func (*DNF) Name() string { func (*DNF) Name() string {
return "dnf" return "dnf"
} }
// Format возвращает формат пакетов "rpm", используемый DNF
func (*DNF) Format() string { func (*DNF) Format() string {
return "rpm" return "rpm"
} }
// SetRootCmd устанавливает команду, используемую для выполнения операций с правами root
func (d *DNF) SetRootCmd(s string) {
d.rootCmd = s
}
// Sync выполняет upgrade всех установленных пакетов, обновляя их до более новых версий // Sync выполняет upgrade всех установленных пакетов, обновляя их до более новых версий
func (d *DNF) Sync(opts *Opts) error { func (d *DNF) Sync(opts *Opts) error {
opts = ensureOpts(opts) // Гарантирует, что opts не равен nil и содержит допустимые значения opts = ensureOpts(opts) // Гарантирует, что opts не равен nil и содержит допустимые значения
@ -118,3 +118,21 @@ func (d *DNF) UpgradeAll(opts *Opts) error {
} }
return nil return nil
} }
// getCmd создает и возвращает команду exec.Cmd для менеджера пакетов DNF
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...)
}
if opts.NoConfirm {
cmd.Args = append(cmd.Args, "-y") // Добавляет параметр автоматического подтверждения (-y)
}
return cmd
}

@ -27,22 +27,27 @@ import (
var Args []string var Args []string
type Opts struct { type Opts struct {
AsRoot bool
NoConfirm bool NoConfirm bool
Args []string Args []string
} }
var DefaultOpts = &Opts{ var DefaultOpts = &Opts{
AsRoot: true,
NoConfirm: false, NoConfirm: false,
} }
// DefaultRootCmd is the command used for privilege elevation by default
var DefaultRootCmd = "sudo"
var managers = []Manager{ var managers = []Manager{
NewPacman(), &Pacman{},
NewAPT(), &APT{},
NewDNF(), &DNF{},
NewYUM(), &YUM{},
NewAPK(), &APK{},
NewZypper(), &Zypper{},
NewAPTRpm(), &APTRpm{},
} }
// Register registers a new package manager // Register registers a new package manager
@ -59,7 +64,8 @@ type Manager interface {
Format() string Format() string
// Returns true if the package manager exists on the system. // Returns true if the package manager exists on the system.
Exists() bool Exists() bool
// Sets the command used to elevate privileges. Defaults to DefaultRootCmd.
SetRootCmd(string)
// Sync fetches repositories without installing anything // Sync fetches repositories without installing anything
Sync(*Opts) error Sync(*Opts) error
// Install installs packages // Install installs packages
@ -98,10 +104,18 @@ func Get(name string) Manager {
return nil return nil
} }
// getRootCmd returns rootCmd if it's not empty, otherwise returns DefaultRootCmd
func getRootCmd(rootCmd string) string {
if rootCmd != "" {
return rootCmd
}
return DefaultRootCmd
}
func setCmdEnv(cmd *exec.Cmd) { func setCmdEnv(cmd *exec.Cmd) {
cmd.Env = os.Environ() cmd.Env = os.Environ()
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stderr cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
} }

@ -28,15 +28,7 @@ import (
// Pacman represents the Pacman package manager // Pacman represents the Pacman package manager
type Pacman struct { type Pacman struct {
CommonPackageManager rootCmd string
}
func NewPacman() *Pacman {
return &Pacman{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "--noconfirm",
},
}
} }
func (*Pacman) Exists() bool { func (*Pacman) Exists() bool {
@ -52,6 +44,10 @@ func (*Pacman) Format() string {
return "archlinux" return "archlinux"
} }
func (p *Pacman) SetRootCmd(s string) {
p.rootCmd = s
}
func (p *Pacman) Sync(opts *Opts) error { func (p *Pacman) Sync(opts *Opts) error {
opts = ensureOpts(opts) opts = ensureOpts(opts)
cmd := p.getCmd(opts, "pacman", "-Sy") cmd := p.getCmd(opts, "pacman", "-Sy")
@ -160,3 +156,20 @@ func (p *Pacman) IsInstalled(pkg string) (bool, error) {
} }
return true, nil return true, nil
} }
func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(p.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)
}
if opts.NoConfirm {
cmd.Args = append(cmd.Args, "--noconfirm")
}
return cmd
}

@ -26,16 +26,9 @@ import (
// YUM represents the YUM package manager // YUM represents the YUM package manager
type YUM struct { type YUM struct {
CommonPackageManager
CommonRPM CommonRPM
}
func NewYUM() *YUM { rootCmd string
return &YUM{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-y",
},
}
} }
func (*YUM) Exists() bool { func (*YUM) Exists() bool {
@ -51,6 +44,10 @@ func (*YUM) Format() string {
return "rpm" return "rpm"
} }
func (y *YUM) SetRootCmd(s string) {
y.rootCmd = s
}
func (y *YUM) Sync(opts *Opts) error { func (y *YUM) Sync(opts *Opts) error {
opts = ensureOpts(opts) opts = ensureOpts(opts)
cmd := y.getCmd(opts, "yum", "upgrade") cmd := y.getCmd(opts, "yum", "upgrade")
@ -113,3 +110,20 @@ func (y *YUM) UpgradeAll(opts *Opts) error {
} }
return nil return nil
} }
func (y *YUM) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(y.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)
}
if opts.NoConfirm {
cmd.Args = append(cmd.Args, "-y")
}
return cmd
}

@ -26,16 +26,8 @@ import (
// Zypper represents the Zypper package manager // Zypper represents the Zypper package manager
type Zypper struct { type Zypper struct {
CommonPackageManager
CommonRPM CommonRPM
} rootCmd string
func NewZypper() *YUM {
return &YUM{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-y",
},
}
} }
func (*Zypper) Exists() bool { func (*Zypper) Exists() bool {
@ -51,6 +43,10 @@ func (*Zypper) Format() string {
return "rpm" return "rpm"
} }
func (z *Zypper) SetRootCmd(s string) {
z.rootCmd = s
}
func (z *Zypper) Sync(opts *Opts) error { func (z *Zypper) Sync(opts *Opts) error {
opts = ensureOpts(opts) opts = ensureOpts(opts)
cmd := z.getCmd(opts, "zypper", "refresh") cmd := z.getCmd(opts, "zypper", "refresh")
@ -113,3 +109,20 @@ func (z *Zypper) UpgradeAll(opts *Opts) error {
} }
return nil return nil
} }
func (z *Zypper) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(z.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)
}
if opts.NoConfirm {
cmd.Args = append(cmd.Args, "-y")
}
return cmd
}

@ -67,7 +67,7 @@ type action struct {
// If repos is set to nil, the repos in the ALR config will be used. // If repos is set to nil, the repos in the ALR config will be used.
func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
if repos == nil { if repos == nil {
repos = rs.cfg.Repos() repos = rs.cfg.Repos(ctx)
} }
for _, repo := range repos { for _, repo := range repos {
@ -77,7 +77,7 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
} }
slog.Info(gotext.Get("Pulling repository"), "name", repo.Name) slog.Info(gotext.Get("Pulling repository"), "name", repo.Name)
repoDir := filepath.Join(rs.cfg.GetPaths().RepoDir, repo.Name) repoDir := filepath.Join(config.GetPaths(ctx).RepoDir, repo.Name)
var repoFS billy.Filesystem var repoFS billy.Filesystem
gitDir := filepath.Join(repoDir, ".git") gitDir := filepath.Join(repoDir, ".git")
@ -264,11 +264,10 @@ func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Ru
return interp.New( return interp.New(
interp.Env(expand.ListEnviron(env...)), interp.Env(expand.ListEnviron(env...)),
interp.ExecHandler(handlers.NopExec), interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler2(handlers.RestrictedReadDir(repoDir)), interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
interp.StatHandler(handlers.RestrictedStat(repoDir)), interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)), interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}), interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
interp.Dir(scriptDir),
) )
} }

@ -32,13 +32,13 @@ import (
type TestALRConfig struct{} type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths() *config.Paths { func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
DBPath: ":memory:", DBPath: ":memory:",
} }
} }
func (c *TestALRConfig) Repos() []types.Repo { func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo {
return []types.Repo{ return []types.Repo{
{ {
Name: "test", Name: "test",

@ -44,7 +44,7 @@ type TestALRConfig struct {
PkgsDir string PkgsDir string
} }
func (c *TestALRConfig) GetPaths() *config.Paths { func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{ return &config.Paths{
DBPath: ":memory:", DBPath: ":memory:",
CacheDir: c.CacheDir, CacheDir: c.CacheDir,
@ -53,7 +53,7 @@ func (c *TestALRConfig) GetPaths() *config.Paths {
} }
} }
func (c *TestALRConfig) Repos() []types.Repo { func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo {
return []types.Repo{} return []types.Repo{}
} }

@ -17,14 +17,16 @@
package repos package repos
import ( import (
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
) )
type Config interface { type Config interface {
GetPaths() *config.Paths GetPaths(ctx context.Context) *config.Paths
Repos() []types.Repo Repos(ctx context.Context) []types.Repo
} }
type Repos struct { type Repos struct {

60
pkg/repos/repos_legacy.go Normal file

@ -0,0 +1,60 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repos
import (
"context"
"sync"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
// Pull pulls the provided repositories. If a repo doesn't exist, it will be cloned
// and its packages will be written to the DB. If it does exist, it will be pulled.
// In this case, only changed packages will be processed if possible.
// If repos is set to nil, the repos in the ALR config will be used.
//
// Deprecated: use struct method
func Pull(ctx context.Context, repos []types.Repo) error {
return GetInstance(ctx).Pull(ctx, repos)
}
// =======================
// FOR LEGACY ONLY
// =======================
var (
reposInstance *Repos
alrConfigOnce sync.Once
)
// Deprecated: For legacy only
func GetInstance(ctx context.Context) *Repos {
alrConfigOnce.Do(func() {
cfg := config.GetInstance(ctx)
db := database.GetInstance(ctx)
reposInstance = New(
cfg,
db,
)
})
return reposInstance
}

@ -79,22 +79,26 @@ type PackageInfo struct {
} }
func (inf *PackageInfo) ToPackage(repoName string) *db.Package { func (inf *PackageInfo) ToPackage(repoName string) *db.Package {
pkg := EmptyPackage(repoName) return &db.Package{
pkg.Version = inf.Version Version: inf.Version,
pkg.Release = inf.Release Release: inf.Release,
pkg.Epoch = inf.Epoch Epoch: inf.Epoch,
pkg.Architectures = inf.Architectures Architectures: inf.Architectures,
pkg.Licenses = inf.Licenses Licenses: inf.Licenses,
pkg.Provides = inf.Provides Provides: inf.Provides,
pkg.Conflicts = inf.Conflicts Conflicts: inf.Conflicts,
pkg.Replaces = inf.Replaces Replaces: inf.Replaces,
return pkg Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repoName,
}
} }
func EmptyPackage(repoName string) *db.Package { func EmptyPackage(repoName string) *db.Package {
return &db.Package{ return &db.Package{
Group: db.NewJSON(map[string]string{}),
Summary: db.NewJSON(map[string]string{}),
Description: db.NewJSON(map[string]string{}), Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}), Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}), Maintainer: db.NewJSON(map[string]string{}),
@ -110,8 +114,6 @@ var overridable = map[string]string{
"desc": "Description", "desc": "Description",
"homepage": "Homepage", "homepage": "Homepage",
"maintainer": "Maintainer", "maintainer": "Maintainer",
"group": "Group",
"summary": "Summary",
} }
func resolveOverrides(runner *interp.Runner, pkg *db.Package) { func resolveOverrides(runner *interp.Runner, pkg *db.Package) {

@ -21,46 +21,166 @@ package search
import ( import (
"context" "context"
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/jmoiron/sqlx" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
) )
type PackagesProvider interface { // Filter represents search filters.
GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) type Filter int
// Filters
const (
FilterNone Filter = iota
FilterInRepo
FilterSupportsArch
)
// SoryBy represents a value that packages can be sorted by.
type SortBy int
// Sort values
const (
SortByNone = iota
SortByName
SortByRepo
SortByVersion
)
// Package represents a package from ALR's database
type Package struct {
Name string
Version string
Release int
Epoch uint
Description map[string]string
Homepage map[string]string
Maintainer map[string]string
Architectures []string
Licenses []string
Provides []string
Conflicts []string
Replaces []string
Depends map[string][]string
BuildDepends map[string][]string
OptDepends map[string][]string
Repository string
} }
type Searcher struct { func convertPkg(p db.Package) Package {
pp PackagesProvider return Package{
} Name: p.Name,
Version: p.Version,
func New(pp PackagesProvider) *Searcher { Release: p.Release,
return &Searcher{ Epoch: p.Epoch,
pp: pp, Description: p.Description.Val,
Homepage: p.Homepage.Val,
Maintainer: p.Maintainer.Val,
Architectures: p.Architectures.Val,
Licenses: p.Licenses.Val,
Provides: p.Provides.Val,
Conflicts: p.Conflicts.Val,
Replaces: p.Replaces.Val,
Depends: p.Depends.Val,
BuildDepends: p.BuildDepends.Val,
OptDepends: p.OptDepends.Val,
Repository: p.Repository,
} }
} }
func (s *Searcher) Search( // Options contains the options for a search.
ctx context.Context, type Options struct {
opts *SearchOptions, Filter Filter
) ([]database.Package, error) { FilterValue string
var packages []database.Package SortBy SortBy
Limit int64
Query string
}
where, args := opts.WhereClause() // Search searches for packages in the database based on the given options.
result, err := s.pp.GetPkgs(ctx, where, args...) func Search(ctx context.Context, opts Options) ([]Package, error) {
query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))"
args := []any{"%" + opts.Query + "%", "%" + opts.Query + "%", opts.Query}
if opts.Filter != FilterNone {
switch opts.Filter {
case FilterInRepo:
query += " AND repository = ?"
case FilterSupportsArch:
query += " AND json_array_contains(architectures, ?)"
}
args = append(args, opts.FilterValue)
}
if opts.SortBy != SortByNone {
switch opts.SortBy {
case SortByName:
query += " ORDER BY name"
case SortByRepo:
query += " ORDER BY repository"
case SortByVersion:
query += " ORDER BY version"
}
}
if opts.Limit != 0 {
query += " LIMIT " + strconv.FormatInt(opts.Limit, 10)
}
result, err := db.GetPkgs(ctx, query, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var out []Package
for result.Next() { for result.Next() {
var dbPkg database.Package pkg := db.Package{}
err = result.StructScan(&dbPkg) err = result.StructScan(&pkg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
packages = append(packages, dbPkg) out = append(out, convertPkg(pkg))
} }
return packages, nil return out, err
}
// GetPkg gets a single package from the database and returns it.
func GetPkg(ctx context.Context, repo, name string) (Package, error) {
pkg, err := db.GetPkg(ctx, "name = ? AND repository = ?", name, repo)
return convertPkg(*pkg), err
}
var (
// ErrInvalidArgument is an error returned by GetScript when one of its arguments
// contain invalid characters
ErrInvalidArgument = errors.New("name and repository must not contain . or /")
// ErrScriptNotFound is returned by GetScript if it can't find the script requested
// by the user.
ErrScriptNotFound = errors.New("requested script not found")
)
// GetScript returns a reader containing the build script for a given package.
func GetScript(ctx context.Context, repo, name string) (io.ReadCloser, error) {
if strings.Contains(name, "./") || strings.ContainsAny(repo, "./") {
return nil, ErrInvalidArgument
}
scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, repo, name, "alr.sh")
fl, err := os.Open(scriptPath)
if errors.Is(err, fs.ErrNotExist) {
return nil, ErrScriptNotFound
} else if err != nil {
return nil, err
}
return fl, nil
} }

@ -1,86 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 search
import (
"fmt"
"strings"
)
type SearchOptions struct {
conditions []string
args []any
}
func (o *SearchOptions) WhereClause() (string, []any) {
if len(o.conditions) == 0 {
return "", nil
}
return strings.Join(o.conditions, " AND "), o.args
}
type SearchOptionsBuilder struct {
options SearchOptions
}
func NewSearchOptions() *SearchOptionsBuilder {
return &SearchOptionsBuilder{}
}
func (b *SearchOptionsBuilder) withGeneralLike(key, value string) *SearchOptionsBuilder {
if value != "" {
b.options.conditions = append(b.options.conditions, fmt.Sprintf("%s LIKE ?", key))
b.options.args = append(b.options.args, "%"+value+"%")
}
return b
}
func (b *SearchOptionsBuilder) withGeneralEqual(key string, value any) *SearchOptionsBuilder {
if value != "" {
b.options.conditions = append(b.options.conditions, fmt.Sprintf("%s = ?", key))
b.options.args = append(b.options.args, value)
}
return b
}
func (b *SearchOptionsBuilder) withGeneralJsonArrayContains(key string, value any) *SearchOptionsBuilder {
if value != "" {
b.options.conditions = append(b.options.conditions, fmt.Sprintf("json_array_contains(%s, ?)", key))
b.options.args = append(b.options.args, value)
}
return b
}
func (b *SearchOptionsBuilder) WithName(name string) *SearchOptionsBuilder {
return b.withGeneralLike("name", name)
}
func (b *SearchOptionsBuilder) WithDescription(description string) *SearchOptionsBuilder {
return b.withGeneralLike("description", description)
}
func (b *SearchOptionsBuilder) WithRepository(repository string) *SearchOptionsBuilder {
return b.withGeneralEqual("repository", repository)
}
func (b *SearchOptionsBuilder) WithProvides(provides string) *SearchOptionsBuilder {
return b.withGeneralJsonArrayContains("provides", provides)
}
func (b *SearchOptionsBuilder) Build() *SearchOptions {
return &b.options
}

@ -1,65 +0,0 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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 search_test
import (
"testing"
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search"
)
func TestSearhOptionsBuilder(t *testing.T) {
type testCase struct {
name string
prepare func() *search.SearchOptions
expectedWhere string
expectedArgs []any
}
for _, tc := range []testCase{
{
name: "Empty fields",
prepare: func() *search.SearchOptions {
return search.NewSearchOptions().
Build()
},
expectedWhere: "",
expectedArgs: []any{},
},
{
name: "All fields",
prepare: func() *search.SearchOptions {
return search.NewSearchOptions().
WithName("foo").
WithDescription("bar").
WithRepository("buz").
WithProvides("test").
Build()
},
expectedWhere: "name LIKE ? AND description LIKE ? AND repository = ? AND json_array_contains(provides, ?)",
expectedArgs: []any{"%foo%", "%bar%", "buz", "test"},
},
} {
t.Run(tc.name, func(t *testing.T) {
whereClause, args := tc.prepare().WhereClause()
assert.Equal(t, tc.expectedWhere, whereClause)
assert.ElementsMatch(t, tc.expectedArgs, args)
})
}
}

Some files were not shown because too many files have changed in this diff Show More