13 Commits

57 changed files with 3882 additions and 1476 deletions

View File

@@ -37,6 +37,7 @@ 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 $< $@

View File

@@ -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">19.4%</text> <text x="86" y="15" fill="#010101" fill-opacity=".3">15.7%</text>
<text x="86" y="14">19.4%</text> <text x="86" y="14">15.7%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 926 B

View File

@@ -12,7 +12,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="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text> <text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text>
<text x="37" y="14">ru translate</text> <text x="37" y="14">ru translate</text>
<text x="100" y="15" fill="#010101" fill-opacity=".3">97.00%</text> <text x="100" y="15" fill="#010101" fill-opacity=".3">98.00%</text>
<text x="100" y="14">97.00%</text> <text x="100" y="14">98.00%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 940 B

After

Width:  |  Height:  |  Size: 940 B

152
build.go
View File

@@ -20,8 +20,10 @@
package main package main
import ( import (
"bytes"
"log/slog" "log/slog"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -32,6 +34,7 @@ import (
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/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/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
@@ -66,9 +69,56 @@ func BuildCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
wd, err := os.Getwd()
if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
}
executable, err := os.Executable()
if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
}
cmd := exec.Command(executable, "_internal-mount", wd)
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
}
wd = stdout.String()
defer func() {
slog.Warn("unmounting...")
cmd := exec.Command(executable, "_internal-umount", wd)
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
}
}()
err = utils.DropCapsToAlrUser()
if err != nil {
slog.Error(gotext.Get("Error dropping capabilities"), "err", err)
os.Exit(1)
}
_, err = os.Stat(wd)
if err != nil {
slog.Error(gotext.Get("Error dropping capabilities"), "err", err)
os.Exit(1)
}
ctx := c.Context ctx := c.Context
cfg := config.New() cfg := config.New()
err := cfg.Load() err = cfg.Load()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err) slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1) os.Exit(1)
@@ -84,14 +134,51 @@ func BuildCmd() *cli.Command {
var script string var script string
var packages []string var packages []string
repository := "default"
repoDir := cfg.GetPaths().RepoDir // Обнаружение менеджера пакетов
mgr := manager.Detect()
if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
}
info, err := distro.ParseOSRelease(ctx)
if err != nil {
slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1)
}
builder := build.NewMainBuilder(
cfg,
rs,
)
var res *build.BuildResult
switch { switch {
case c.IsSet("script"): case c.IsSet("script"):
script = c.String("script") script = c.String("script")
packages = append(packages, c.String("script-package")) packages = append(packages, c.String("script-package"))
res, err = builder.BuildPackageFromScript(
ctx,
&build.BuildPackageFromScriptArgs{
Script: script,
Packages: packages,
BuildArgs: build.BuildArgs{
Opts: &types.BuildOpts{
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
},
PkgFormat_: build.GetPkgFormat(mgr),
Info: info,
},
},
)
if err != nil {
slog.Error(gotext.Get("Error building package"), "err", err)
os.Exit(1)
}
case c.IsSet("package"): case c.IsSet("package"):
// TODO: handle multiple packages // TODO: handle multiple packages
packageInput := c.String("package") packageInput := c.String("package")
@@ -111,71 +198,36 @@ func BuildCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
repository = pkg[0].Repository
if pkg[0].BasePkgName != "" { if pkg[0].BasePkgName != "" {
script = filepath.Join(repoDir, repository, pkg[0].BasePkgName, "alr.sh")
packages = append(packages, pkg[0].Name) packages = append(packages, pkg[0].Name)
} else {
script = filepath.Join(repoDir, repository, pkg[0].Name, "alr.sh")
}
default:
script = filepath.Join(repoDir, "alr.sh")
} }
// Проверка автоматического пулла репозиториев res, err = builder.BuildPackageFromDb(
if cfg.AutoPull() {
err := rs.Pull(ctx, cfg.Repos())
if err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1)
}
}
// Обнаружение менеджера пакетов
mgr := manager.Detect()
if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
}
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, ctx,
types.BuildOpts{ &build.BuildPackageFromDbArgs{
Package: &pkg[0],
Packages: packages, Packages: packages,
Repository: repository, BuildArgs: build.BuildArgs{
Script: script, Opts: &types.BuildOpts{
Manager: mgr,
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
}, },
rs, PkgFormat_: build.GetPkgFormat(mgr),
info, Info: info,
cfg, },
},
) )
// Сборка пакета
pkgPaths, _, err := builder.BuildPackage(ctx)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error building package"), "err", err) slog.Error(gotext.Get("Error building package"), "err", err)
os.Exit(1) os.Exit(1)
} }
default:
// Получение текущей рабочей директории slog.Error(gotext.Get("Nothing to build"))
wd, err := os.Getwd()
if err != nil {
slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1) os.Exit(1)
} }
// Перемещение собранных пакетов в рабочую директорию // Перемещение собранных пакетов в рабочую директорию
for _, pkgPath := range pkgPaths { for _, pkgPath := range res.PackagePaths {
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 {

View File

@@ -33,6 +33,7 @@ func TestE2EAlrAddRepo(t *testing.T) {
COMMON_SYSTEMS, COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) { func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand( err := r.Exec(e2e.NewCommand(
"sudo",
"alr", "alr",
"addrepo", "addrepo",
"--name", "--name",
@@ -45,11 +46,12 @@ func TestE2EAlrAddRepo(t *testing.T) {
err = r.Exec(e2e.NewCommand( err = r.Exec(e2e.NewCommand(
"bash", "bash",
"-c", "-c",
"cat $HOME/.config/alr/alr.toml", "cat /etc/alr/alr.toml",
)) ))
assert.NoError(t, err) assert.NoError(t, err)
err = r.Exec(e2e.NewCommand( err = r.Exec(e2e.NewCommand(
"sudo",
"alr", "alr",
"removerepo", "removerepo",
"--name", "--name",
@@ -61,7 +63,7 @@ func TestE2EAlrAddRepo(t *testing.T) {
err = r.Exec(e2e.NewCommand( err = r.Exec(e2e.NewCommand(
"bash", "bash",
"-c", "-c",
"cat $HOME/.config/alr/alr.toml", "cat /etc/alr/alr.toml",
), e2e.WithExecOptionStdout(&buf)) ), e2e.WithExecOptionStdout(&buf))
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, buf.String(), "rootCmd") assert.Contains(t, buf.String(), "rootCmd")

View File

@@ -0,0 +1,40 @@
// 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)
},
)
}

View File

@@ -100,13 +100,19 @@ func e2eSpawn(runnable e2e.Runnable, command e2e.Command, timeout time.Duration,
var ALL_SYSTEMS []string = []string{ var ALL_SYSTEMS []string = []string{
"ubuntu-24.04", "ubuntu-24.04",
// "alt-sisyphus", "alt-sisyphus",
"fedora-41",
// "archlinux", // "archlinux",
// "alpine", // "alpine",
// "opensuse-leap", // "opensuse-leap",
// "redos-8", // "redos-8",
} }
var AUTOREQ_AUTOPROV_SYSTEMS []string = []string{
// "alt-sisyphus",
"fedora-41",
}
var COMMON_SYSTEMS []string = []string{ var COMMON_SYSTEMS []string = []string{
"ubuntu-24.04", "ubuntu-24.04",
} }
@@ -153,7 +159,7 @@ func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testin
e2e.StartOptions{ e2e.StartOptions{
Image: imageId, Image: imageId,
Volumes: []string{ Volumes: []string{
"./alr:/usr/bin/alr", // "./alr:/usr/bin/alr",
}, },
Privileged: true, Privileged: true,
}, },

View File

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

View File

@@ -0,0 +1,17 @@
FROM fedora:41
RUN dnf install -y ca-certificates sudo rpm-build
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"]

View File

@@ -1,7 +1,17 @@
FROM ubuntu:24.10 FROM ubuntu:24.10
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates sudo RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates sudo libcap2-bin
RUN useradd -m -s /bin/bash alr-user && \ RUN <<EOF
echo "alr-user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/alr-user && \ useradd -m -s /bin/bash user
chmod 0440 /etc/sudoers.d/alr-user echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/user
USER alr-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"] ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@@ -0,0 +1,81 @@
// 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)
},
)
}

View File

@@ -32,6 +32,7 @@ func TestE2EIssue50InstallMultiple(t *testing.T) {
COMMON_SYSTEMS, COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) { func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand( err := r.Exec(e2e.NewCommand(
"sudo",
"alr", "alr",
"addrepo", "addrepo",
"--name", "--name",

View File

@@ -32,6 +32,7 @@ func TestE2EIssue53LcAllCInfo(t *testing.T) {
COMMON_SYSTEMS, COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) { func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand( err := r.Exec(e2e.NewCommand(
"sudo",
"alr", "alr",
"addrepo", "addrepo",
"--name", "--name",

View File

@@ -0,0 +1,58 @@
// 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(
"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)
},
)
}

80
fix.go
View File

@@ -22,13 +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/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/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func FixCmd() *cli.Command { func FixCmd() *cli.Command {
@@ -36,51 +36,67 @@ 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 {
ctx := c.Context if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
cfg := config.New() return err
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
} }
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
Build()
if err != nil {
return cli.Exit(err, 1)
}
defer deps.Defer()
cfg := deps.Cfg
paths := cfg.GetPaths() paths := cfg.GetPaths()
slog.Info(gotext.Get("Removing cache directory")) slog.Info(gotext.Get("Clearing cache directory"))
// Remove all nested directories of paths.CacheDir
err = os.RemoveAll(paths.CacheDir) dir, err := os.Open(paths.CacheDir)
if err != nil { if err != nil {
slog.Error(gotext.Get("Unable to remove cache directory"), "err", err) slog.Error(gotext.Get("Unable to open cache directory"))
os.Exit(1) return cli.Exit(err, 1)
}
defer dir.Close()
entries, err := dir.Readdirnames(-1)
if err != nil {
slog.Error(gotext.Get("Unable to read cache directory contents"))
return cli.Exit(err, 1)
}
for _, entry := range entries {
err = os.RemoveAll(filepath.Join(paths.CacheDir, entry))
if err != nil {
slog.Error(gotext.Get("Unable to remove cache item"), "item", entry)
return cli.Exit(err, 1)
}
} }
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 {
slog.Error(gotext.Get("Unable to create new cache directory"), "err", err) slog.Error(gotext.Get("Unable to create new cache directory"))
os.Exit(1) return cli.Exit(err, 1)
} }
cfg = config.New() deps, err = appbuilder.
err = cfg.Load() New(ctx).
WithConfig().
WithDB().
WithRepos().
Build()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err) return cli.Exit(err, 1)
os.Exit(1)
}
db := database.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)
err = rs.Pull(ctx, cfg.Repos())
if err != nil {
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"))

17
go.mod
View File

@@ -4,8 +4,9 @@ go 1.22
toolchain go1.23.5 toolchain go1.23.5
replace github.com/creack/pty => github.com/creack/pty v1.1.19
require ( require (
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/assert/v2 v2.2.1
@@ -15,11 +16,14 @@ require (
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/creack/pty v1.1.24
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 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.5.0
github.com/go-git/go-git/v5 v5.12.0 github.com/go-git/go-git/v5 v5.12.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
@@ -33,7 +37,7 @@ require (
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.27.0 golang.org/x/crypto v0.32.0
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
golang.org/x/sys v0.29.0 golang.org/x/sys v0.29.0
golang.org/x/text v0.21.0 golang.org/x/text v0.21.0
@@ -72,10 +76,12 @@ require (
github.com/efficientgo/core v1.0.0-rc.0 // 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/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
@@ -84,6 +90,7 @@ require (
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/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
@@ -103,6 +110,7 @@ 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
@@ -121,10 +129,13 @@ require (
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.18.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.26.0 // indirect golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.10.0 // indirect golang.org/x/sync v0.10.0 // indirect
golang.org/x/term v0.28.0 // indirect golang.org/x/term v0.28.0 // indirect
golang.org/x/tools v0.22.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

58
go.sum
View File

@@ -17,8 +17,6 @@ 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.1 h1:c7F4OsyQbiVpSOrYGMrNsRL37BwoOfrgoKxAwULBKZo=
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=
@@ -71,6 +69,8 @@ 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/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs= 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/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
@@ -79,8 +79,8 @@ github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTli
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.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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=
@@ -106,9 +106,8 @@ github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEyc
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
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.19 h1:tUN6H7LWqNx4hQVxomd0CVsDwaDr9gaRQaI4GpSmrsA=
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= github.com/creack/pty v1.1.19/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/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=
@@ -133,6 +132,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
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.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
@@ -171,8 +172,9 @@ 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.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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=
@@ -181,6 +183,7 @@ 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 h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
@@ -211,10 +214,16 @@ 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=
@@ -229,6 +238,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 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
@@ -264,9 +275,11 @@ 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=
@@ -304,6 +317,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 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/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
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=
@@ -405,8 +420,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
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.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
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=
@@ -458,15 +473,15 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
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.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= 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=
@@ -487,6 +502,7 @@ 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=
@@ -572,8 +588,8 @@ 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.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 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=
@@ -587,6 +603,8 @@ 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=
@@ -594,8 +612,12 @@ 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/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 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.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=

72
info.go
View File

@@ -30,11 +30,12 @@ import (
"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" "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/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 {
@@ -49,6 +50,11 @@ func InfoCmd() *cli.Command {
}, },
}, },
BashComplete: func(c *cli.Context) { BashComplete: func(c *cli.Context) {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
slog.Error("Can't drop caps")
os.Exit(1)
}
ctx := c.Context ctx := c.Context
cfg := config.New() cfg := config.New()
err := cfg.Load() err := cfg.Load()
@@ -83,45 +89,39 @@ func InfoCmd() *cli.Command {
} }
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
cfg := config.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
} }
db := database.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 {
slog.Error(gotext.Get("Command info expected at least 1 argument, got %d", args.Len())) return cli.Exit(gotext.Get("Command info expected at least 1 argument, got %d", args.Len()), 1)
os.Exit(1)
} }
if cfg.AutoPull() { ctx := c.Context
err := rs.Pull(ctx, cfg.Repos())
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithRepos().
Build()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err) return cli.Exit(err, 1)
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 {
slog.Error(gotext.Get("Error finding packages"), "err", err) slog.Error(gotext.Get("Error finding packages"))
os.Exit(1) return cli.Exit(err, 1)
} }
if len(found) == 0 { if len(found) == 0 {
os.Exit(1) slog.Error(gotext.Get("Package not found"))
return cli.Exit(err, 1)
} }
pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive")) pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive"))
@@ -131,8 +131,8 @@ func InfoCmd() *cli.Command {
systemLang, err := locale.GetLanguage() systemLang, err := locale.GetLanguage()
if err != nil { if err != nil {
slog.Error("Can't detect system language", "err", err) slog.Error(gotext.Get("Can't detect system language"))
os.Exit(1) return cli.Exit(err, 1)
} }
if systemLang == "" { if systemLang == "" {
systemLang = "en" systemLang = "en"
@@ -141,8 +141,8 @@ func InfoCmd() *cli.Command {
if !all { if !all {
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error parsing os-release file"), "err", err) slog.Error(gotext.Get("Error parsing os-release file"))
os.Exit(1) return cli.Exit(err, 1)
} }
names, err = overrides.Resolve( names, err = overrides.Resolve(
info, info,
@@ -150,8 +150,8 @@ func InfoCmd() *cli.Command {
WithLanguages([]string{systemLang}), WithLanguages([]string{systemLang}),
) )
if err != nil { if err != nil {
slog.Error(gotext.Get("Error resolving overrides"), "err", err) slog.Error(gotext.Get("Error resolving overrides"))
os.Exit(1) return cli.Exit(err, 1)
} }
} }
@@ -159,14 +159,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 {
slog.Error(gotext.Get("Error encoding script variables"), "err", err) slog.Error(gotext.Get("Error encoding script variables"))
os.Exit(1) return cli.Exit(err, 1)
} }
} else { } else {
err = yaml.NewEncoder(os.Stdout).Encode(pkg) err = yaml.NewEncoder(os.Stdout).Encode(pkg)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error encoding script variables"), "err", err) slog.Error(gotext.Get("Error encoding script variables"))
os.Exit(1) return cli.Exit(err, 1)
} }
} }

View File

@@ -27,10 +27,10 @@ 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" "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/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
@@ -79,6 +79,17 @@ func InstallCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
err = utils.DropCapsToAlrUser()
if err != nil {
slog.Error(gotext.Get("Error dropping capabilities"), "err", err)
os.Exit(1)
}
builder := build.NewMainBuilder(
cfg,
rs,
)
if cfg.AutoPull() { if cfg.AutoPull() {
err := rs.Pull(ctx, cfg.Repos()) err := rs.Pull(ctx, cfg.Repos())
if err != nil { if err != nil {
@@ -87,45 +98,41 @@ func InstallCmd() *cli.Command {
} }
} }
found, notFound, err := rs.FindPkgs(ctx, args.Slice())
if err != nil {
slog.Error(gotext.Get("Error finding packages"), "err", err)
os.Exit(1)
}
pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive"))
opts := types.BuildOpts{
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
}
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error parsing os release"), "err", err) slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1) os.Exit(1)
} }
builder := build.NewBuilder( err = builder.InstallPkgs(
ctx, ctx,
opts, &build.BuildArgs{
rs, Opts: &types.BuildOpts{
info,
cfg,
)
builder.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
Manager: mgr,
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
}) },
Info: info,
PkgFormat_: build.GetPkgFormat(mgr),
},
args.Slice(),
)
if err != nil {
slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1)
}
return nil return nil
}, },
BashComplete: func(c *cli.Context) { BashComplete: func(c *cli.Context) {
cfg := config.New() cfg := config.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg) db := database.New(cfg)
err := db.Init(c.Context) err = db.Init(c.Context)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err) slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1) os.Exit(1)
@@ -156,6 +163,64 @@ 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: func(c *cli.Context) {
cfg := config.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg)
err = db.Init(c.Context)
if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
}
installedAlrPackages := map[string]string{}
mgr := manager.Detect()
if mgr == nil {
slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
}
installed, err := mgr.ListInstalled(&manager.Opts{AsRoot: false})
if err != nil {
slog.Error(gotext.Get("Error listing installed packages"), "err", err)
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
}
}
result, err := db.GetPkgs(c.Context, "true")
if err != nil {
slog.Error(gotext.Get("Error getting packages"), "err", err)
os.Exit(1)
}
defer result.Close()
for result.Next() {
var pkg database.Package
err = result.StructScan(&pkg)
if err != nil {
slog.Error(gotext.Get("Error iterating over packages"), "err", err)
os.Exit(1)
}
_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
if !ok {
continue
}
fmt.Println(pkg.Name)
}
},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
args := c.Args() args := c.Args()
if args.Len() < 1 { if args.Len() < 1 {

266
internal.go Normal file
View File

@@ -0,0 +1,266 @@
// 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 (
"fmt"
"log/slog"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"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/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"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"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
)
func InternalBuildCmd() *cli.Command {
return &cli.Command{
Name: "_internal-safe-script-executor",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
err := utils.DropCapsToAlrUser()
if err != nil {
slog.Error("aa", "err", err)
os.Exit(1)
}
cfg := config.New()
err = cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
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()
err := syscall.Setuid(0)
if err != nil {
slog.Error("err")
os.Exit(1)
}
cfg := config.New()
err = cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg)
rs := repos.New(cfg, db)
err = db.Init(c.Context)
if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
}
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(
rs,
manager.Detect(),
),
},
},
Logger: logger,
})
return nil
},
}
}
func InternalMountCmd() *cli.Command {
return &cli.Command{
Name: "_internal-mount",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
sourceDir := c.Args().First()
u, _ := user.Current()
logger.SetupForGoPlugin()
err := syscall.Setuid(0)
if err != nil {
slog.Error("Failed to setuid(0)", "err", err)
os.Exit(1)
}
alrRunDir := "/var/run/alr"
err = os.MkdirAll(alrRunDir, 0o750)
if err != nil {
slog.Error("Error creating /var/run/alr directory", "err", err)
os.Exit(1)
}
_, gid, _ := utils.GetUidGidAlrUser()
// Меняем группу на alr и права
err = os.Chown(alrRunDir, 0, gid) // root:alr
if err != nil {
slog.Error("Failed to chown /var/run/alr", "err", err)
os.Exit(1)
}
// Создаем поддиректорию для bindfs
targetDir := filepath.Join(alrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid()))
err = os.MkdirAll(targetDir, 0o750) // 0750: владелец (root) и группа (alr) имеют доступ
if err != nil {
slog.Error("Error creating bindfs target directory", "err", err)
os.Exit(1)
}
// Устанавливаем владельца и группу (root:alr)
err = os.Chown(targetDir, 0, gid)
if err != nil {
slog.Error("Failed to chown bindfs directory", "err", err)
os.Exit(1)
}
bindfsCmd := exec.Command(
"bindfs",
fmt.Sprintf("--map=%s/alr:@%s/@alr", u.Uid, u.Gid),
sourceDir,
targetDir,
)
bindfsCmd.Stderr = os.Stderr
if err := bindfsCmd.Start(); err != nil {
slog.Error("Error starting bindfs", "err", err)
os.Exit(1)
}
fmt.Print(targetDir)
return nil
},
}
}
func InternalUnmountCmd() *cli.Command {
return &cli.Command{
Name: "_internal-umount",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
currentUser, err := user.Current()
if err != nil {
slog.Error("Failed to get current user", "err", err)
os.Exit(1)
}
uid, gid, err := utils.GetUidGidAlrUserString()
if err != nil {
slog.Error("Failed to get alr user info", "err", err)
os.Exit(1)
}
if currentUser.Uid != uid && currentUser.Gid != gid {
slog.Error("Only alr user can unmount these directories")
os.Exit(1)
}
targetDir := c.Args().First()
if targetDir == "" {
slog.Error("No target directory specified")
os.Exit(1)
}
if !strings.HasPrefix(targetDir, "/var/run/alr/") {
slog.Error("Can only unmount directories under /var/run/alr")
os.Exit(1)
}
if _, err := os.Stat(targetDir); os.IsNotExist(err) {
slog.Error("Target directory does not exist", "dir", targetDir)
os.Exit(1)
}
err = syscall.Setuid(0)
if err != nil {
slog.Error("Failed to setuid(0)", "err", err)
os.Exit(1)
}
umountCmd := exec.Command("umount", targetDir)
umountCmd.Stderr = os.Stderr
if err := umountCmd.Run(); err != nil {
slog.Error("Error unmounting directory", "dir", targetDir, "err", err)
os.Exit(1)
}
if err := os.Remove(targetDir); err != nil {
slog.Error("Error removing directory", "dir", targetDir, "err", err)
os.Exit(1)
}
return nil
},
}
}

View File

@@ -0,0 +1,126 @@
// 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"
"github.com/urfave/cli/v2"
"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/repos"
)
type AppDeps struct {
Cfg *config.ALRConfig
DB *db.Database
Repos *repos.Repos
}
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) WithConfig() *AppBuilder {
if b.err != nil {
return b
}
cfg := config.New()
if err := cfg.Load(); err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
b.err = cli.Exit("", 1)
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 {
slog.Error(gotext.Get("Error initialization database"), "err", err)
b.err = cli.Exit("", 1)
return b
}
b.deps.DB = db
return b
}
func (b *AppBuilder) WithRepos() *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 cfg.AutoPull() {
if err := rs.Pull(b.ctx, cfg.Repos()); err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err)
b.err = cli.Exit("", 1)
return b
}
}
b.deps.Repos = rs
return b
}
func (b *AppBuilder) Build() (*AppDeps, error) {
if b.err != nil {
return nil, b.err
}
return &b.deps, nil
}

View File

@@ -26,7 +26,6 @@ import (
"reflect" "reflect"
"github.com/caarlos0/env" "github.com/caarlos0/env"
"github.com/leonelquinteros/gotext"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
@@ -84,7 +83,10 @@ func mergeStructs(dst, src interface{}) {
} }
} }
const systemConfigPath = "/etc/alr/alr.toml" const (
systemConfigPath = "/etc/alr/alr.toml"
systemCachePath = "/var/cache/alr"
)
func (c *ALRConfig) Load() error { func (c *ALRConfig) Load() error {
systemConfig, err := readConfig( systemConfig, err := readConfig(
@@ -94,24 +96,10 @@ func (c *ALRConfig) Load() error {
slog.Debug("Cannot read system config", "err", err) slog.Debug("Cannot read system config", "err", err)
} }
cfgDir, err := os.UserConfigDir()
if err != nil {
slog.Debug("Cannot read user config directory")
}
userConfigPath := filepath.Join(cfgDir, "alr", "alr.toml")
userConfig, err := readConfig(
userConfigPath,
)
if err != nil {
slog.Debug("Cannot read user config")
}
config := &types.Config{} config := &types.Config{}
mergeStructs(config, defaultConfig) mergeStructs(config, defaultConfig)
mergeStructs(config, systemConfig) mergeStructs(config, systemConfig)
mergeStructs(config, userConfig)
err = env.Parse(config) err = env.Parse(config)
if err != nil { if err != nil {
return err return err
@@ -119,17 +107,13 @@ func (c *ALRConfig) Load() error {
c.cfg = config c.cfg = config
cacheDir, err := os.UserCacheDir()
if err != nil {
return err
}
c.paths = &Paths{} c.paths = &Paths{}
c.paths.UserConfigPath = userConfigPath c.paths.UserConfigPath = systemConfigPath
c.paths.CacheDir = filepath.Join(cacheDir, "alr") c.paths.CacheDir = systemCachePath
c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo") c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo")
c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs") c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs")
c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db") c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db")
c.initPaths() // c.initPaths()
return nil return nil
} }
@@ -170,26 +154,6 @@ func (c *ALRConfig) GetPaths() *Paths {
return c.paths return c.paths
} }
func (c *ALRConfig) initPaths() {
err := os.MkdirAll(filepath.Dir(c.paths.UserConfigPath), 0o755)
if err != nil {
slog.Error(gotext.Get("Unable to create config directory"), "err", err)
os.Exit(1)
}
err = os.MkdirAll(c.paths.RepoDir, 0o755)
if err != nil {
slog.Error(gotext.Get("Unable to create repo cache directory"), "err", err)
os.Exit(1)
}
err = os.MkdirAll(c.paths.PkgsDir, 0o755)
if err != nil {
slog.Error(gotext.Get("Unable to create package cache directory"), "err", err)
os.Exit(1)
}
}
func (c *ALRConfig) SaveUserConfig() error { func (c *ALRConfig) SaveUserConfig() error {
f, err := os.Create(c.paths.UserConfigPath) f, err := os.Create(c.paths.UserConfigPath)
if err != nil { if err != nil {

136
internal/logger/hclog.go Normal file
View File

@@ -0,0 +1,136 @@
// 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"
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])
}
}
a.logger.l.Log(hclogLevelTochLog(level), 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,
}
}

View File

@@ -22,96 +22,90 @@ 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 {
lOut slog.Handler l *chLog.Logger
lErr slog.Handler
} }
func setupOutLogger() *log.Logger { func setupLogger() *chLog.Logger {
styles := log.DefaultStyles() styles := chLog.DefaultStyles()
logger := log.New(os.Stdout) logger := chLog.New(os.Stderr)
styles.Levels[log.InfoLevel] = lipgloss.NewStyle(). styles.Levels[chLog.InfoLevel] = lipgloss.NewStyle().
SetString("-->"). SetString("-->").
Foreground(lipgloss.Color("35")) Foreground(lipgloss.Color("35"))
logger.SetStyles(styles) styles.Levels[chLog.ErrorLevel] = lipgloss.NewStyle().
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{
lOut: standardLogger, l: setupLogger(),
lErr: errLogger,
} }
} }
func slogLevelToLog(level slog.Level) log.Level { func slogLevelToLog(level slog.Level) chLog.Level {
switch level { switch level {
case slog.LevelDebug: case slog.LevelDebug:
return log.DebugLevel return chLog.DebugLevel
case slog.LevelInfo: case slog.LevelInfo:
return log.InfoLevel return chLog.InfoLevel
case slog.LevelWarn: case slog.LevelWarn:
return log.WarnLevel return chLog.WarnLevel
case slog.LevelError: case slog.LevelError:
return log.ErrorLevel return chLog.ErrorLevel
} }
return log.FatalLevel return chLog.FatalLevel
} }
func (l *Logger) SetLevel(level slog.Level) { func (l *Logger) SetLevel(level slog.Level) {
l.lOut.(*log.Logger).SetLevel(slogLevelToLog(level)) l.l.SetLevel(slogLevelToLog(level))
l.lErr.(*log.Logger).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 {
if level <= slog.LevelInfo { return l.l.Enabled(ctx, level)
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 {
if rec.Level <= slog.LevelInfo { return l.l.Handle(ctx, rec)
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.lOut = l.lOut.WithAttrs(attrs) sl.l = l.l.WithAttrs(attrs).(*chLog.Logger)
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.lOut = l.lOut.WithGroup(name) sl.l = l.l.WithGroup(name).(*chLog.Logger)
sl.lErr = l.lErr.WithGroup(name)
return &sl return &sl
} }
var logger *Logger
func SetupDefault() *Logger { func SetupDefault() *Logger {
l := New() logger = New()
logger := slog.New(l) slogLogger := slog.New(logger)
slog.SetDefault(logger) slog.SetDefault(slogLogger)
return l return logger
}
func SetupForGoPlugin() {
logger.l.SetFormatter(chLog.JSONFormatter)
chLog.TimestampKey = "@timestamp"
chLog.MessageKey = "@message"
chLog.LevelKey = "@level"
}
func GetLogger() *Logger {
return logger
} }

View File

@@ -123,15 +123,29 @@ 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
@@ -160,7 +174,6 @@ func (d *Decoder) DecodeVars(val any) error {
field.Set(newVal.Elem()) field.Set(newVal.Elem())
} }
return nil return nil
} }

View File

@@ -25,11 +25,11 @@ import (
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
"slices"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"gitea.plemya-x.ru/Plemya-x/fakeroot"
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
) )
@@ -54,7 +54,7 @@ func FakerootExecHandler(killTimeout time.Duration) interp.ExecHandlerFunc {
Stderr: hc.Stderr, Stderr: hc.Stderr,
} }
err = fakeroot.Apply(cmd) err = Apply(cmd)
if err != nil { if err != nil {
return err return err
} }
@@ -108,6 +108,52 @@ func FakerootExecHandler(killTimeout time.Duration) interp.ExecHandlerFunc {
} }
} }
func rootMap(m syscall.SysProcIDMap) bool {
return m.ContainerID == 0
}
func Apply(cmd *exec.Cmd) error {
uid := os.Getuid()
gid := os.Getgid()
// If the user is already root, there's no need for fakeroot
if uid == 0 {
return nil
}
// Ensure SysProcAttr isn't nil
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
// Create a new user namespace
cmd.SysProcAttr.Cloneflags |= syscall.CLONE_NEWUSER
// If the command already contains a mapping for the root user, return an error
if slices.ContainsFunc(cmd.SysProcAttr.UidMappings, rootMap) {
return nil
}
// If the command already contains a mapping for the root group, return an error
if slices.ContainsFunc(cmd.SysProcAttr.GidMappings, rootMap) {
return nil
}
cmd.SysProcAttr.UidMappings = append(cmd.SysProcAttr.UidMappings, syscall.SysProcIDMap{
ContainerID: 0,
HostID: uid,
Size: 1,
})
cmd.SysProcAttr.GidMappings = append(cmd.SysProcAttr.GidMappings, syscall.SysProcIDMap{
ContainerID: 0,
HostID: gid,
Size: 1,
})
return nil
}
// execEnv was extracted from github.com/mvdan/sh/interp/vars.go // execEnv was extracted from github.com/mvdan/sh/interp/vars.go
func execEnv(env expand.Environ) []string { func execEnv(env expand.Environ) []string {
list := make([]string, 0, 64) list := make([]string, 0, 64)

View File

@@ -9,60 +9,64 @@ 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:44 #: build.go:47
msgid "Build a local package" msgid "Build a local package"
msgstr "" msgstr ""
#: build.go:50 #: build.go:53
msgid "Path to the build script" msgid "Path to the build script"
msgstr "" msgstr ""
#: build.go:55 #: build.go:58
msgid "Specify subpackage in script (for multi package script only)" msgid "Specify subpackage in script (for multi package script only)"
msgstr "" msgstr ""
#: build.go:60 #: build.go:63
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:65 #: build.go:68
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:74 build.go:79 build.go:89 build.go:103
msgid "Error loading config"
msgstr ""
#: build.go:81
msgid "Error initialization database"
msgstr ""
#: build.go:110
msgid "Package not found"
msgstr ""
#: build.go:130
msgid "Error pulling repositories"
msgstr ""
#: build.go:138
msgid "Unable to detect a supported package manager on the system"
msgstr ""
#: build.go:144
msgid "Error parsing os release"
msgstr ""
#: build.go:166
msgid "Error building package"
msgstr ""
#: build.go:173
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "" msgstr ""
#: build.go:182 #: build.go:110 build.go:115
msgid "Error dropping capabilities"
msgstr ""
#: build.go:123
msgid "Error loading config"
msgstr ""
#: build.go:131
msgid "Error initialization database"
msgstr ""
#: build.go:141
msgid "Unable to detect a supported package manager on the system"
msgstr ""
#: build.go:147
msgid "Error parsing os release"
msgstr ""
#: build.go:179 build.go:221
msgid "Error building package"
msgstr ""
#: build.go:197
msgid "Package not found"
msgstr ""
#: build.go:225
msgid "Nothing to build"
msgstr ""
#: build.go:234
msgid "Error moving the package" msgid "Error moving the package"
msgstr "" msgstr ""
@@ -70,27 +74,31 @@ msgstr ""
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "" msgstr ""
#: fix.go:49 #: fix.go:58
msgid "Removing cache directory" msgid "Clearing cache directory"
msgstr "" msgstr ""
#: fix.go:53 #: fix.go:63
msgid "Unable to remove cache directory" msgid "Unable to open cache directory"
msgstr "" msgstr ""
#: fix.go:57 #: fix.go:70
msgid "Unable to read cache directory contents"
msgstr ""
#: fix.go:77
msgid "Unable to remove cache item"
msgstr ""
#: fix.go:82
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "" msgstr ""
#: fix.go:61 #: fix.go:86
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "" msgstr ""
#: fix.go:81 #: fix.go:101
msgid "Error pulling repos"
msgstr ""
#: fix.go:85
msgid "Done" msgid "Done"
msgstr "" msgstr ""
@@ -118,30 +126,34 @@ msgstr ""
msgid "No such helper command" msgid "No such helper command"
msgstr "" msgstr ""
#: info.go:43 #: info.go:44
msgid "Print information about a package" msgid "Print information about a package"
msgstr "" msgstr ""
#: info.go:48 #: info.go:49
msgid "Show all information, not just for the current distro" msgid "Show all information, not just for the current distro"
msgstr "" msgstr ""
#: info.go:69 #: info.go:75
msgid "Error getting packages" msgid "Error getting packages"
msgstr "" msgstr ""
#: info.go:78 #: info.go:84
msgid "Error iterating over packages" msgid "Error iterating over packages"
msgstr "" msgstr ""
#: info.go:105 #: info.go:98
msgid "Command info expected at least 1 argument, got %d" msgid "Command info expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: info.go:119 #: info.go:118
msgid "Error finding packages" msgid "Error finding packages"
msgstr "" msgstr ""
#: info.go:134
msgid "Can't detect system language"
msgstr ""
#: info.go:144 #: info.go:144
msgid "Error parsing os-release file" msgid "Error parsing os-release file"
msgstr "" msgstr ""
@@ -162,15 +174,23 @@ msgstr ""
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:157 #: install.go:96
msgid "Error pulling repositories"
msgstr ""
#: install.go:164
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "" msgstr ""
#: install.go:162 #: install.go:189
msgid "Error listing installed packages"
msgstr ""
#: install.go:227
msgid "Command remove expected at least 1 argument, got %d" msgid "Command remove expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:177 #: install.go:242
msgid "Error removing packages" msgid "Error removing packages"
msgstr "" msgstr ""
@@ -254,18 +274,6 @@ msgstr ""
msgid "OPTIONS" msgid "OPTIONS"
msgstr "" msgstr ""
#: internal/config/config.go:176
msgid "Unable to create config directory"
msgstr ""
#: internal/config/config.go:182
msgid "Unable to create repo cache directory"
msgstr ""
#: internal/config/config.go:188
msgid "Unable to create package cache directory"
msgstr ""
#: internal/db/db.go:133 #: internal/db/db.go:133
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "" msgstr ""
@@ -299,130 +307,100 @@ msgstr ""
msgid "%s %s downloading at %s/s\n" msgid "%s %s downloading at %s/s\n"
msgstr "" msgstr ""
#: internal/logger/log.go:47 #: internal/logger/log.go:41
msgid "ERROR" msgid "ERROR"
msgstr "" msgstr ""
#: internal/utils/cmd.go:94
msgid "You need to be root to perform this action"
msgstr ""
#: list.go:41 #: list.go:41
msgid "List ALR repo packages" msgid "List ALR repo packages"
msgstr "" msgstr ""
#: list.go:98
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 ""
#: main.go:61 #: main.go:79
msgid "Arguments to be passed on to the package manager" msgid "Arguments to be passed on to the package manager"
msgstr "" msgstr ""
#: main.go:67 #: main.go:85
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "" msgstr ""
#: main.go:96 #: main.go:185
msgid ""
"Running ALR as root is forbidden as it may cause catastrophic damage to your "
"system"
msgstr ""
#: main.go:154
msgid "Show help" msgid "Show help"
msgstr "" msgstr ""
#: main.go:158 #: main.go:189
msgid "Error while running app" msgid "Error while running app"
msgstr "" msgstr ""
#: pkg/build/build.go:156 #: pkg/build/build.go:392
msgid "Failed to prompt user to view build script"
msgstr ""
#: pkg/build/build.go:160
msgid "Building package" msgid "Building package"
msgstr "" msgstr ""
#: pkg/build/build.go:208 #: pkg/build/build.go:421
msgid "The checksums array must be the same length as sources" msgid "The checksums array must be the same length as sources"
msgstr "" msgstr ""
#: pkg/build/build.go:235 #: pkg/build/build.go:448
msgid "Downloading sources" msgid "Downloading sources"
msgstr "" msgstr ""
#: pkg/build/build.go:257 #: pkg/build/build.go:535
msgid "Building package metadata" msgid "Installing dependencies"
msgstr "" msgstr ""
#: pkg/build/build.go:279 #: pkg/build/checker.go:43
msgid "Compressing package"
msgstr ""
#: pkg/build/build.go:438
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/build.go:452 #: pkg/build/checker.go:67
msgid "This package is already installed" msgid "This package is already installed"
msgstr "" msgstr ""
#: pkg/build/build.go:476 #: pkg/build/find_deps/alt_linux.go:35
msgid "Installing build dependencies"
msgstr ""
#: pkg/build/build.go:521
msgid "Installing dependencies"
msgstr ""
#: pkg/build/build.go:602
msgid "Would you like to remove the build dependencies?"
msgstr ""
#: pkg/build/build.go:665
msgid "Executing prepare()"
msgstr ""
#: pkg/build/build.go:675
msgid "Executing build()"
msgstr ""
#: pkg/build/build.go:705 pkg/build/build.go:725
msgid "Executing %s()"
msgstr ""
#: pkg/build/build.go:784
msgid "Error installing native packages"
msgstr ""
#: pkg/build/build.go:808
msgid "Error installing package"
msgstr ""
#: pkg/build/build.go:867
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/build.go:878
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/findDeps.go:35
msgid "Command not found on the system" msgid "Command not found on the system"
msgstr "" msgstr ""
#: pkg/build/findDeps.go:82 #: pkg/build/find_deps/alt_linux.go:86
msgid "Provided dependency found" msgid "Provided dependency found"
msgstr "" msgstr ""
#: pkg/build/findDeps.go:89 #: pkg/build/find_deps/alt_linux.go:93
msgid "Required dependency found" msgid "Required dependency found"
msgstr "" msgstr ""
#: pkg/build/find_deps/empty.go:32
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/find_deps/empty.go:37
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:356
msgid "Executing prepare()"
msgstr ""
#: pkg/build/script_executor.go:365
msgid "Executing build()"
msgstr ""
#: pkg/build/script_executor.go:394 pkg/build/script_executor.go:414
msgid "Executing %s()"
msgstr ""
#: pkg/repos/pull.go:79 #: pkg/repos/pull.go:79
msgid "Pulling repository" msgid "Pulling repository"
msgstr "" msgstr ""
@@ -441,43 +419,51 @@ msgid ""
"updating ALR if something doesn't work." "updating ALR if something doesn't work."
msgstr "" msgstr ""
#: repo.go:40 #: repo.go:41
msgid "Add a new repository" msgid "Add a new repository"
msgstr "" msgstr ""
#: repo.go:47 #: repo.go:48
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "" msgstr ""
#: repo.go:53 #: repo.go:54
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "" msgstr ""
#: repo.go:86 repo.go:156 #: repo.go:92 repo.go:172
msgid "Error saving config" msgid "Error saving config"
msgstr "" msgstr ""
#: repo.go:111 #: repo.go:97 repo.go:199
msgid "Can't drop privileges"
msgstr ""
#: repo.go:104 repo.go:110 repo.go:219
msgid "Error pulling repos"
msgstr ""
#: repo.go:122
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "" msgstr ""
#: repo.go:118 #: repo.go:129
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "" msgstr ""
#: repo.go:142 #: repo.go:158
msgid "Repo does not exist" msgid "Repo does not exist"
msgstr "" msgstr ""
#: repo.go:150 #: repo.go:166
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "" msgstr ""
#: repo.go:167 #: repo.go:183
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "" msgstr ""
#: repo.go:179 #: repo.go:195
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "" msgstr ""
@@ -505,22 +491,26 @@ msgstr ""
msgid "Format output using a Go template" msgid "Format output using a Go template"
msgstr "" msgstr ""
#: search.go:88 search.go:105 #: search.go:96
msgid "Error while executing search"
msgstr ""
#: search.go:105
msgid "Error parsing format template" msgid "Error parsing format template"
msgstr "" msgstr ""
#: search.go:113 #: search.go:114
msgid "Error executing template" msgid "Error executing template"
msgstr "" msgstr ""
#: upgrade.go:47 #: upgrade.go:48
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "" msgstr ""
#: upgrade.go:96 #: upgrade.go:111 upgrade.go:129
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "" msgstr ""
#: upgrade.go:118 #: upgrade.go:133
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "" msgstr ""

View File

@@ -16,61 +16,67 @@ msgstr ""
"%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 47.1\n" "X-Generator: Gtranslator 47.1\n"
#: build.go:44 #: build.go:47
msgid "Build a local package" msgid "Build a local package"
msgstr "Сборка локального пакета" msgstr "Сборка локального пакета"
#: build.go:50 #: build.go:53
msgid "Path to the build script" msgid "Path to the build script"
msgstr "Путь к скрипту сборки" msgstr "Путь к скрипту сборки"
#: build.go:55 #: build.go:58
msgid "Specify subpackage in script (for multi package script only)" msgid "Specify subpackage in script (for multi package script only)"
msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)" msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)"
#: build.go:60 #: build.go:63
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:65 #: build.go:68
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:74 build.go:79 build.go:89 build.go:103
msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога"
#: build.go:110 build.go:115
#, fuzzy
msgid "Error dropping capabilities"
msgstr "Ошибка при открытии базы данных"
#: build.go:123
#, fuzzy #, fuzzy
msgid "Error loading config" msgid "Error loading config"
msgstr "Ошибка при кодировании конфигурации" msgstr "Ошибка при кодировании конфигурации"
#: build.go:81 #: build.go:131
msgid "Error initialization database" msgid "Error initialization database"
msgstr "Ошибка инициализации базы данных" msgstr "Ошибка инициализации базы данных"
#: build.go:110 #: build.go:141
msgid "Package not found"
msgstr "Пакет не найден"
#: build.go:130
msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев"
#: build.go:138
msgid "Unable to detect a supported package manager on the system" msgid "Unable to detect a supported package manager on the system"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
#: build.go:144 #: build.go:147
msgid "Error parsing os release" msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы" msgstr "Ошибка при разборе файла выпуска операционной системы"
#: build.go:166 #: build.go:179 build.go:221
msgid "Error building package" msgid "Error building package"
msgstr "Ошибка при сборке пакета" msgstr "Ошибка при сборке пакета"
#: build.go:173 #: build.go:197
msgid "Error getting working directory" msgid "Package not found"
msgstr "Ошибка при получении рабочего каталога" msgstr "Пакет не найден"
#: build.go:182 #: build.go:225
#, fuzzy
msgid "Nothing to build"
msgstr "Исполнение build()"
#: build.go:234
msgid "Error moving the package" msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета" msgstr "Ошибка при перемещении пакета"
@@ -78,27 +84,35 @@ msgstr "Ошибка при перемещении пакета"
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR" msgstr "Попытка устранить проблемы с ALR"
#: fix.go:49 #: fix.go:58
msgid "Removing cache directory" #, fuzzy
msgid "Clearing cache directory"
msgstr "Удаление каталога кэша" msgstr "Удаление каталога кэша"
#: fix.go:53 #: fix.go:63
msgid "Unable to remove cache directory" #, fuzzy
msgid "Unable to open cache directory"
msgstr "Не удалось удалить каталог кэша" msgstr "Не удалось удалить каталог кэша"
#: fix.go:57 #: fix.go:70
#, fuzzy
msgid "Unable to read cache directory contents"
msgstr "Не удалось удалить каталог кэша"
#: fix.go:77
#, fuzzy
msgid "Unable to remove cache item"
msgstr "Не удалось удалить каталог кэша"
#: fix.go:82
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "Восстановление кэша" msgstr "Восстановление кэша"
#: fix.go:61 #: fix.go:86
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "Не удалось создать новый каталог кэша" msgstr "Не удалось создать новый каталог кэша"
#: fix.go:81 #: fix.go:101
msgid "Error pulling repos"
msgstr "Ошибка при извлечении репозиториев"
#: fix.go:85
msgid "Done" msgid "Done"
msgstr "Сделано" msgstr "Сделано"
@@ -126,30 +140,35 @@ msgstr "Каталог, в который будут устанавливать
msgid "No such helper command" msgid "No such helper command"
msgstr "Такой вспомогательной команды нет" msgstr "Такой вспомогательной команды нет"
#: info.go:43 #: info.go:44
msgid "Print information about a package" msgid "Print information about a package"
msgstr "Отобразить информацию о пакете" msgstr "Отобразить информацию о пакете"
#: info.go:48 #: info.go:49
msgid "Show all information, not just for the current distro" msgid "Show all information, not just for the current distro"
msgstr "Показывать всю информацию, не только для текущего дистрибутива" msgstr "Показывать всю информацию, не только для текущего дистрибутива"
#: info.go:69 #: info.go:75
msgid "Error getting packages" msgid "Error getting packages"
msgstr "Ошибка при получении пакетов" msgstr "Ошибка при получении пакетов"
#: info.go:78 #: info.go:84
msgid "Error iterating over packages" msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов" msgstr "Ошибка при переборе пакетов"
#: info.go:105 #: info.go:98
msgid "Command info expected at least 1 argument, got %d" msgid "Command info expected at least 1 argument, got %d"
msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d"
#: info.go:119 #: info.go:118
msgid "Error finding packages" msgid "Error finding packages"
msgstr "Ошибка при поиске пакетов" msgstr "Ошибка при поиске пакетов"
#: info.go:134
#, fuzzy
msgid "Can't detect system language"
msgstr "Ошибка при парсинге языка системы"
#: info.go:144 #: info.go:144
msgid "Error parsing os-release file" msgid "Error parsing os-release file"
msgstr "Ошибка при разборе файла выпуска операционной системы" msgstr "Ошибка при разборе файла выпуска операционной системы"
@@ -170,15 +189,23 @@ msgstr "Установить новый пакет"
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
#: install.go:157 #: install.go:96
msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев"
#: install.go:164
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "Удалить установленный пакет" msgstr "Удалить установленный пакет"
#: install.go:162 #: install.go:189
msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов"
#: install.go:227
msgid "Command remove expected at least 1 argument, got %d" msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
#: install.go:177 #: install.go:242
msgid "Error removing packages" msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов" msgstr "Ошибка при удалении пакетов"
@@ -262,19 +289,6 @@ msgstr "КАТЕГОРИЯ"
msgid "OPTIONS" msgid "OPTIONS"
msgstr "ПАРАМЕТРЫ" msgstr "ПАРАМЕТРЫ"
#: internal/config/config.go:176
#, fuzzy
msgid "Unable to create config directory"
msgstr "Не удалось создать каталог конфигурации ALR"
#: internal/config/config.go:182
msgid "Unable to create repo cache directory"
msgstr "Не удалось создать каталог кэша репозитория"
#: internal/config/config.go:188
msgid "Unable to create package cache directory"
msgstr "Не удалось создать каталог кэша пакетов"
#: internal/db/db.go:133 #: internal/db/db.go:133
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "Несоответствие версий базы данных; сброс настроек" msgstr "Несоответствие версий базы данных; сброс настроек"
@@ -309,71 +323,55 @@ msgstr "%s: выполнено!\n"
msgid "%s %s downloading at %s/s\n" msgid "%s %s downloading at %s/s\n"
msgstr "%s %s загружается — %s/с\n" msgstr "%s %s загружается — %s/с\n"
#: internal/logger/log.go:47 #: internal/logger/log.go:41
msgid "ERROR" msgid "ERROR"
msgstr "ОШИБКА" msgstr "ОШИБКА"
#: internal/utils/cmd.go:94
msgid "You need to be root to perform this action"
msgstr ""
#: list.go:41 #: list.go:41
msgid "List ALR repo packages" msgid "List ALR repo packages"
msgstr "Список пакетов репозитория ALR" msgstr "Список пакетов репозитория ALR"
#: list.go:98
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 и выйти"
#: main.go:61 #: main.go:79
msgid "Arguments to be passed on to the package manager" msgid "Arguments to be passed on to the package manager"
msgstr "Аргументы, которые будут переданы менеджеру пакетов" msgstr "Аргументы, которые будут переданы менеджеру пакетов"
#: main.go:67 #: main.go:85
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов" msgstr "Включение интерактивных вопросов и запросов"
#: main.go:96 #: main.go:185
msgid ""
"Running ALR as root is forbidden as it may cause catastrophic damage to your "
"system"
msgstr ""
"Запуск ALR от имени root запрещён, так как это может привести к "
"катастрофическому повреждению вашей системы"
#: main.go:154
msgid "Show help" msgid "Show help"
msgstr "Показать справку" msgstr "Показать справку"
#: main.go:158 #: main.go:189
msgid "Error while running app" msgid "Error while running app"
msgstr "Ошибка при запуске приложения" msgstr "Ошибка при запуске приложения"
#: pkg/build/build.go:156 #: pkg/build/build.go:392
msgid "Failed to prompt user to view build script"
msgstr "Не удалось предложить пользователю просмотреть скрипт сборки"
#: pkg/build/build.go:160
msgid "Building package" msgid "Building package"
msgstr "Сборка пакета" msgstr "Сборка пакета"
#: pkg/build/build.go:208 #: pkg/build/build.go:421
msgid "The checksums array must be the same length as sources" msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники" msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
#: pkg/build/build.go:235 #: pkg/build/build.go:448
msgid "Downloading sources" msgid "Downloading sources"
msgstr "Скачивание источников" msgstr "Скачивание источников"
#: pkg/build/build.go:257 #: pkg/build/build.go:535
msgid "Building package metadata" msgid "Installing dependencies"
msgstr "Сборка метаданных пакета" msgstr "Установка зависимостей"
#: pkg/build/build.go:279 #: pkg/build/checker.go:43
msgid "Compressing package"
msgstr "Сжатие пакета"
#: pkg/build/build.go:438
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?"
@@ -381,63 +379,47 @@ msgstr ""
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все " "Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
"равно хотите выполнить сборку?" "равно хотите выполнить сборку?"
#: pkg/build/build.go:452 #: pkg/build/checker.go:67
msgid "This package is already installed" msgid "This package is already installed"
msgstr "Этот пакет уже установлен" msgstr "Этот пакет уже установлен"
#: pkg/build/build.go:476 #: pkg/build/find_deps/alt_linux.go:35
msgid "Installing build dependencies" msgid "Command not found on the system"
msgstr "Установка зависимостей сборки" msgstr "Команда не найдена в системе"
#: pkg/build/build.go:521 #: pkg/build/find_deps/alt_linux.go:86
msgid "Installing dependencies" msgid "Provided dependency found"
msgstr "Установка зависимостей" msgstr "Найденная предоставленная зависимость"
#: pkg/build/build.go:602 #: pkg/build/find_deps/alt_linux.go:93
msgid "Would you like to remove the build dependencies?" msgid "Required dependency found"
msgstr "Хотели бы вы удалить зависимости сборки?" msgstr "Найдена требуемая зависимость"
#: pkg/build/build.go:665 #: pkg/build/find_deps/empty.go:32
msgid "Executing prepare()"
msgstr "Исполнение prepare()"
#: pkg/build/build.go:675
msgid "Executing build()"
msgstr "Исполнение build()"
#: pkg/build/build.go:705 pkg/build/build.go:725
msgid "Executing %s()"
msgstr "Исполнение %s()"
#: pkg/build/build.go:784
msgid "Error installing native packages"
msgstr "Ошибка при установке нативных пакетов"
#: pkg/build/build.go:808
msgid "Error installing package"
msgstr "Ошибка при установке пакета"
#: pkg/build/build.go:867
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/build.go:878 #: pkg/build/find_deps/empty.go:37
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/findDeps.go:35 #: pkg/build/script_executor.go:237
msgid "Command not found on the system" msgid "Building package metadata"
msgstr "Команда не найдена в системе" msgstr "Сборка метаданных пакета"
#: pkg/build/findDeps.go:82 #: pkg/build/script_executor.go:356
msgid "Provided dependency found" msgid "Executing prepare()"
msgstr "Найденная предоставленная зависимость" msgstr "Исполнение prepare()"
#: pkg/build/findDeps.go:89 #: pkg/build/script_executor.go:365
msgid "Required dependency found" msgid "Executing build()"
msgstr "Найдена требуемая зависимость" msgstr "Исполнение build()"
#: pkg/build/script_executor.go:394 pkg/build/script_executor.go:414
msgid "Executing %s()"
msgstr "Исполнение %s()"
#: pkg/repos/pull.go:79 #: pkg/repos/pull.go:79
msgid "Pulling repository" msgid "Pulling repository"
@@ -459,44 +441,52 @@ msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте " "Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает." "обновить ALR, если что-то не работает."
#: repo.go:40 #: repo.go:41
msgid "Add a new repository" msgid "Add a new repository"
msgstr "Добавить новый репозиторий" msgstr "Добавить новый репозиторий"
#: repo.go:47 #: repo.go:48
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "Название нового репозитория" msgstr "Название нового репозитория"
#: repo.go:53 #: repo.go:54
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория" msgstr "URL-адрес нового репозитория"
#: repo.go:86 repo.go:156 #: repo.go:92 repo.go:172
#, fuzzy #, fuzzy
msgid "Error saving config" msgid "Error saving config"
msgstr "Ошибка при кодировании конфигурации" msgstr "Ошибка при кодировании конфигурации"
#: repo.go:111 #: repo.go:97 repo.go:199
msgid "Can't drop privileges"
msgstr ""
#: repo.go:104 repo.go:110 repo.go:219
msgid "Error pulling repos"
msgstr "Ошибка при извлечении репозиториев"
#: repo.go:122
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий" msgstr "Удалить существующий репозиторий"
#: repo.go:118 #: repo.go:129
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён" msgstr "Название репозитория удалён"
#: repo.go:142 #: repo.go:158
msgid "Repo does not exist" msgid "Repo does not exist"
msgstr "Репозитория не существует" msgstr "Репозитория не существует"
#: repo.go:150 #: repo.go:166
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория" msgstr "Ошибка при удалении каталога репозитория"
#: repo.go:167 #: repo.go:183
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных" msgstr "Ошибка при удалении пакетов из базы данных"
#: repo.go:179 #: repo.go:195
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории" msgstr "Скачать все изменённые репозитории"
@@ -524,26 +514,66 @@ msgstr "Иcкать по provides"
msgid "Format output using a Go template" msgid "Format output using a Go template"
msgstr "Формат выходных данных с использованием шаблона Go" msgstr "Формат выходных данных с использованием шаблона Go"
#: search.go:88 search.go:105 #: search.go:96
#, fuzzy
msgid "Error while executing search"
msgstr "Ошибка при запуске приложения"
#: search.go:105
msgid "Error parsing format template" msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона" msgstr "Ошибка при разборе шаблона"
#: search.go:113 #: search.go:114
msgid "Error executing template" msgid "Error executing template"
msgstr "Ошибка при выполнении шаблона" msgstr "Ошибка при выполнении шаблона"
#: upgrade.go:47 #: upgrade.go:48
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты" msgstr "Обновить все установленные пакеты"
#: upgrade.go:96 #: upgrade.go:111 upgrade.go:129
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений" msgstr "Ошибка при проверке обновлений"
#: upgrade.go:118 #: upgrade.go:133
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "Здесь нечего делать." 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" #~ msgid "Error opening config file, using defaults"
#~ msgstr "" #~ msgstr ""
#~ "Ошибка при открытии конфигурационного файла, используются значения по " #~ "Ошибка при открытии конфигурационного файла, используются значения по "
@@ -569,12 +599,6 @@ msgstr "Здесь нечего делать."
#~ msgid "Error opening config file" #~ msgid "Error opening config file"
#~ msgstr "Ошибка при открытии конфигурационного файла" #~ msgstr "Ошибка при открытии конфигурационного файла"
#~ msgid "Error parsing system language"
#~ msgstr "Ошибка при парсинге языка системы"
#~ msgid "Error opening database"
#~ msgstr "Ошибка при открытии базы данных"
#~ msgid "Executing version()" #~ msgid "Executing version()"
#~ msgstr "Исполнение версия()" #~ msgstr "Исполнение версия()"

View File

@@ -19,13 +19,11 @@
package types package types
import "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
type BuildOpts struct { type BuildOpts struct {
Script string // Script string
Repository string // Repository string
Packages []string // Packages []string
Manager manager.Manager // Manager manager.Manager
Clean bool Clean bool
Interactive bool Interactive bool
} }
@@ -51,31 +49,15 @@ 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: "",
Version: bv.Version, Base: "",
Release: bv.Release, BuildVarsPre: *bv,
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,
} }
} }
@@ -83,27 +65,8 @@ 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"`
Version string `sh:"version,required"`
Release int `sh:"release,required"`
Epoch uint `sh:"epoch"`
Description string `sh:"desc"`
Homepage string `sh:"homepage"`
Maintainer string `sh:"maintainer"`
Architectures []string `sh:"architectures"`
Licenses []string `sh:"license"`
Provides []string `sh:"provides"`
Conflicts []string `sh:"conflicts"`
Depends []string `sh:"deps"`
BuildDepends []string `sh:"build_deps"`
OptDepends []string `sh:"opt_deps"`
Replaces []string `sh:"replaces"`
Sources []string `sh:"sources"`
Checksums []string `sh:"checksums"`
Backup []string `sh:"backup"`
Scripts Scripts `sh:"scripts"`
AutoReq []string `sh:"auto_req"`
AutoProv []string `sh:"auto_prov"`
Base string Base string
BuildVarsPre
} }
type Scripts struct { type Scripts struct {

97
internal/utils/cmd.go Normal file
View File

@@ -0,0 +1,97 @@
// 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"
"log/slog"
"os"
"os/user"
"strconv"
"syscall"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
)
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
}
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
}
// Returns cli.Exit to
func ExitIfCantDropCapsToAlrUser() cli.ExitCoder {
err := DropCapsToAlrUser()
if err != nil {
slog.Debug("dropping capabilities error", "err", err)
return cli.Exit(gotext.Get("Error dropping capabilities"), 1)
}
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
}

37
list.go
View File

@@ -28,11 +28,11 @@ import (
"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/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/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,29 +47,26 @@ func ListCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
ctx := c.Context ctx := c.Context
cfg := config.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg) deps, err := appbuilder.
err = db.Init(ctx) New(ctx).
WithConfig().
WithDB().
// autoPull only
WithRepos().
Build()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err) return err
os.Exit(1)
} }
rs := repos.New(cfg, db) defer deps.Defer()
if cfg.AutoPull() { cfg := deps.Cfg
err = rs.Pull(ctx, cfg.Repos()) db := deps.DB
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)

33
main.go
View File

@@ -21,10 +21,10 @@ package main
import ( import (
"context" "context"
"fmt"
"log/slog" "log/slog"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
@@ -50,6 +50,24 @@ func VersionCmd() *cli.Command {
} }
} }
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
}
}
func GetApp() *cli.App { func GetApp() *cli.App {
return &cli.App{ return &cli.App{
Name: "alr", Name: "alr",
@@ -82,8 +100,15 @@ func GetApp() *cli.App {
HelperCmd(), HelperCmd(),
VersionCmd(), VersionCmd(),
SearchCmd(), SearchCmd(),
// TEST
InternalBuildCmd(),
InternalInstallCmd(),
InternalMountCmd(),
InternalUnmountCmd(),
// InternalBuild2Cmd(),
}, },
Before: func(c *cli.Context) error { Before: func(c *cli.Context) error {
/*
cfg := config.New() cfg := config.New()
err := cfg.Load() err := cfg.Load()
if err != nil { if err != nil {
@@ -91,20 +116,26 @@ func GetApp() *cli.App {
os.Exit(1) os.Exit(1)
} }
/*
cmd := c.Args().First() cmd := c.Args().First()
if cmd != "helper" && !cfg.AllowRunAsRoot() && os.Geteuid() == 0 { if cmd != "helper" && !cfg.AllowRunAsRoot() && os.Geteuid() == 0 {
slog.Error(gotext.Get("Running ALR as root is forbidden as it may cause catastrophic damage to your system")) slog.Error(gotext.Get("Running ALR as root is forbidden as it may cause catastrophic damage to your system"))
os.Exit(1) 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 return nil
}, },
EnableBashCompletion: true, EnableBashCompletion: true,
ExitErrHandler: func(c *cli.Context, err error) {
},
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -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.executeFirstPass(fl) _, allVars, err := b.scriptExecutor.ExecuteSecondPass(fl)
assert.NoError(t, err) assert.NoError(t, err)
tc.Expected(t, allVars) tc.Expected(t, allVars)

69
pkg/build/cache.go Normal file
View File

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

74
pkg/build/checker.go Normal file
View File

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

71
pkg/build/dirs.go Normal file
View File

@@ -0,0 +1,71 @@
// 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)
}

View File

@@ -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 build package finddeps
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 rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, updateFunc func(string)) error { func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, envs []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 rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Dir
return nil return nil
} }
cmd := exec.Command(command) cmd := exec.CommandContext(ctx, command)
cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n")) cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n") + "\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,6 +58,7 @@ func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Dir
"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
@@ -66,6 +67,7 @@ func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Dir
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 {
@@ -77,15 +79,17 @@ func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Dir
return nil return nil
} }
func rpmFindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error { type ALTLinuxFindProvReq struct{}
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 rpmFindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error { func (o *ALTLinuxFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependencies(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", func(dep string) { return rpmFindDependenciesALTLinux(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", []string{"RPM_FINDREQ_SKIPLIST=" + strings.Join(skiplist, "\n")}, 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)
}) })

View File

@@ -0,0 +1,39 @@
// 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"
"log/slog"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
type EmptyFindProvReq struct{}
func (o *EmptyFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
slog.Info(gotext.Get("AutoProv is not implemented for this package format, so it's skipped"))
return nil
}
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"))
return nil
}

View File

@@ -0,0 +1,118 @@
// 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)
})
}

View File

@@ -0,0 +1,58 @@
// 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)
}

61
pkg/build/installer.go Normal file
View File

@@ -0,0 +1,61 @@
// 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(
repos PackageFinder,
mgr manager.Manager,
) *Installer {
return &Installer{
repos: repos,
mgr: mgr,
}
}
type Installer struct {
repos PackageFinder
mgr manager.Manager
}
func (i *Installer) InstallLocal(paths []string) error {
return i.mgr.InstallLocal(nil, paths...)
}
func (i *Installer) Install(pkgs []string) error {
return i.mgr.Install(nil, 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
}

65
pkg/build/main_build.go Normal file
View File

@@ -0,0 +1,65 @@
// 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 (
"log/slog"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
func NewMainBuilder(
cfg Config,
repos PackageFinder,
) *Builder {
s, err := GetSafeScriptExecutor()
if err != nil {
slog.Info("i will panic")
panic(err)
}
mgr := manager.Detect()
installerExecutor, err := GetSafeInstaller()
if err != nil {
slog.Info("i will panic")
panic(err)
}
builder := &Builder{
scriptExecutor: s,
cacheExecutor: &Cache{
cfg,
},
scriptResolver: &ScriptResolver{
cfg,
},
scriptViewerExecutor: &ScriptViewer{
config: cfg,
},
checkerExecutor: &Checker{
mgr,
},
installerExecutor: installerExecutor,
sourceExecutor: &SourceDownloader{
cfg,
},
repos: repos,
}
return builder
}

263
pkg/build/safe.go Normal file
View File

@@ -0,0 +1,263 @@
// 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"
"net/rpc"
"os"
"os/exec"
"syscall"
"github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
)
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, error) {
executable, err := os.Executable()
if err != nil {
return nil, err
}
cmd := exec.Command(executable, "_internal-safe-script-executor")
cmd.Env = []string{
"HOME=/var/cache/alr",
"LOGNAME=alr",
"USER=alr",
"PATH=/usr/bin:/bin:/usr/local/bin",
}
uid, gid, err := utils.GetUidGidAlrUser()
if err != nil {
return nil, err
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
},
}
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig,
Plugins: pluginMap,
Cmd: cmd,
Logger: logger.GetHCLoggerAdapter(),
SkipHostEnv: true,
})
rpcClient, err := client.Client()
if err != nil {
slog.Info("1")
return nil, err
}
raw1, err := rpcClient.Dispense("script-executor")
if err != nil {
return nil, err
}
return raw1.(ScriptExecutor), nil
}

132
pkg/build/safe_installer.go Normal file
View File

@@ -0,0 +1,132 @@
// 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 (
"log/slog"
"net/rpc"
"os"
"os/exec"
"syscall"
"github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
)
type InstallerPlugin struct {
Impl InstallerExecutor
}
type InstallerRPC struct {
client *rpc.Client
}
type InstallerRPCServer struct {
Impl InstallerExecutor
}
func (r *InstallerRPC) InstallLocal(paths []string) error {
return r.client.Call("Plugin.InstallLocal", paths, nil)
}
func (s *InstallerRPCServer) InstallLocal(paths []string, reply *struct{}) error {
slog.Warn("install", "paths", paths)
return s.Impl.InstallLocal(paths)
}
func (r *InstallerRPC) Install(pkgs []string) error {
return r.client.Call("Plugin.Install", pkgs, nil)
}
func (s *InstallerRPCServer) Install(pkgs []string, reply *struct{}) error {
slog.Debug("install", "pkgs", pkgs)
return s.Impl.Install(pkgs)
}
func (r *InstallerRPC) RemoveAlreadyInstalled(paths []string) ([]string, error) {
err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, nil)
return nil, 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, error) {
executable, err := os.Executable()
if err != nil {
return nil, err
}
cmd := exec.Command(executable, "_internal-installer")
cmd.Env = append(os.Environ(),
"HOME=/var/cache/alr",
"LOGNAME=alr",
"USER=alr",
"PATH=/usr/bin:/bin:/usr/local/bin",
"ALR_LOG_LEVEL=DEBUG",
"XDG_SESSION_CLASS=user",
)
uid, gid, err := utils.GetUidGidAlrUser()
if err != nil {
return nil, err
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
},
}
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 {
slog.Info("1")
return nil, err
}
raw1, err := rpcClient.Dispense("installer")
if err != nil {
return nil, err
}
return raw1.(InstallerExecutor), nil
}

View File

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

View File

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

46
pkg/build/script_view.go Normal file
View File

@@ -0,0 +1,46 @@
// 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,
)
}

View File

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

View File

@@ -39,7 +39,6 @@ import (
"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/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"
@@ -173,19 +172,23 @@ func buildContents(vars *types.BuildVars, dirs types.Directories, preferedConten
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`) var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`)
func getBasePkgInfo(vars *types.BuildVars, info *distro.OSRelease, opts *types.BuildOpts) *nfpm.Info { func getBasePkgInfo(vars *types.BuildVars, input interface {
RepositoryProvider
OsInfoProvider
},
) *nfpm.Info {
return &nfpm.Info{ return &nfpm.Info{
Name: fmt.Sprintf("%s+alr-%s", vars.Name, opts.Repository), Name: fmt.Sprintf("%s+alr-%s", vars.Name, input.Repository()),
Arch: cpu.Arch(), Arch: cpu.Arch(),
Version: vars.Version, Version: vars.Version,
Release: overrides.ReleasePlatformSpecific(vars.Release, info), Release: overrides.ReleasePlatformSpecific(vars.Release, input.OSRelease()),
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10), Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
} }
} }
// Функция 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
@@ -272,25 +275,9 @@ 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 {
@@ -298,6 +285,7 @@ func packageNames(pkgs []db.Package) []string {
} }
return names return names
} }
*/
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза. // Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
func removeDuplicates(slice []string) []string { func removeDuplicates(slice []string) []string {

View File

@@ -82,6 +82,7 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
interp.ReadDirHandler2(handlers.NopReadDir), interp.ReadDirHandler2(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

View File

@@ -66,6 +66,7 @@ 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)

View File

@@ -268,6 +268,7 @@ func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Ru
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),
) )
} }

25
repo.go
View File

@@ -31,6 +31,7 @@ import (
"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"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
@@ -54,13 +55,18 @@ func AddRepoCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
err := utils.ExitIfNotRoot()
if err != nil {
return err
}
ctx := c.Context ctx := c.Context
name := c.String("name") name := c.String("name")
repoURL := c.String("url") repoURL := c.String("url")
cfg := config.New() cfg := config.New()
err := cfg.Load() err = cfg.Load()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err) slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1) os.Exit(1)
@@ -87,6 +93,11 @@ func AddRepoCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
if utils.DropCapsToAlrUser() != nil {
slog.Error(gotext.Get("Can't drop privileges"))
os.Exit(1)
}
db := database.New(cfg) db := database.New(cfg)
err = db.Init(ctx) err = db.Init(ctx)
if err != nil { if err != nil {
@@ -119,11 +130,16 @@ func RemoveRepoCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
err := utils.ExitIfNotRoot()
if err != nil {
return err
}
ctx := c.Context ctx := c.Context
name := c.String("name") name := c.String("name")
cfg := config.New() cfg := config.New()
err := cfg.Load() err = cfg.Load()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err) slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1) os.Exit(1)
@@ -179,6 +195,11 @@ func RefreshCmd() *cli.Command {
Usage: gotext.Get("Pull all repositories that have changed"), Usage: gotext.Get("Pull all repositories that have changed"),
Aliases: []string{"ref"}, Aliases: []string{"ref"},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if utils.DropCapsToAlrUser() != nil {
slog.Error(gotext.Get("Can't drop privileges"))
os.Exit(1)
}
ctx := c.Context ctx := c.Context
cfg := config.New() cfg := config.New()
err := cfg.Load() err := cfg.Load()

View File

@@ -97,14 +97,15 @@ if [ -z "$noPkgMgr" ]; then
echo "Полученный список файлов:" echo "Полученный список файлов:"
echo "$fileList" echo "$fileList"
if [ "$pkgMgr" == "pacman" ]; then if [ "$pkgMgr" == "pacman" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.pkg.tar.zst' | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.pkg\.tar\.zst' | sort -V | tail -n 1)
elif [ "$pkgMgr" == "apt" ]; then elif [ "$pkgMgr" == "apt" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.amd64.deb' | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.amd64\.deb' | sort -V | tail -n 1)
elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then elif [ "$pkgMgr" == "apt-get" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.x86_64\.rpm' | grep -v 'alt1' | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*-alt[0-9]+\.x86_64\.rpm' | sort -V | tail -n 1)
elif [[ "$pkgMgr" == "apt-get" ]]; then elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*alt1.x86_64.rpm' | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.x86_64\.rpm' | sort -V | tail -n 1)
fi
else else
error "Не поддерживаемый менеджер пакетов для автоматической установки" error "Не поддерживаемый менеджер пакетов для автоматической установки"

View File

@@ -25,8 +25,8 @@ 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/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/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/search"
) )
@@ -63,32 +63,23 @@ func SearchCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
ctx := c.Context ctx := c.Context
cfg := config.New()
err := cfg.Load()
if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1)
}
db := database.New(cfg)
err = db.Init(ctx)
defer db.Close()
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
Build()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err) return err
os.Exit(1)
} }
defer deps.Defer()
format := c.String("format") db := deps.DB
var tmpl *template.Template
if format != "" {
tmpl, err = template.New("format").Parse(format)
if err != nil {
slog.Error(gotext.Get("Error parsing format template"), "err", err)
os.Exit(1)
}
}
s := search.New(db) s := search.New(db)
@@ -102,16 +93,26 @@ func SearchCmd() *cli.Command {
Build(), Build(),
) )
if err != nil { if err != nil {
slog.Error(gotext.Get("Error parsing format template"), "err", err) slog.Error(gotext.Get("Error while executing search"))
os.Exit(1) return cli.Exit(err, 1)
}
format := c.String("format")
var tmpl *template.Template
if format != "" {
tmpl, err = template.New("format").Parse(format)
if err != nil {
slog.Error(gotext.Get("Error parsing format template"))
return cli.Exit(err, 1)
}
} }
for _, dbPkg := range packages { for _, dbPkg := range packages {
if tmpl != nil { if tmpl != nil {
err = tmpl.Execute(os.Stdout, dbPkg) err = tmpl.Execute(os.Stdout, dbPkg)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error executing template"), "err", err) slog.Error(gotext.Get("Error executing template"))
os.Exit(1) return cli.Exit(err, 1)
} }
fmt.Println() fmt.Println()
} else { } else {

View File

@@ -34,6 +34,7 @@ import (
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/overrides" "gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"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/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
@@ -54,10 +55,16 @@ func UpgradeCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
err := utils.DropCapsToAlrUser()
if err != nil {
slog.Error(gotext.Get("Error dropping capabilities"), "err", err)
os.Exit(1)
}
ctx := c.Context ctx := c.Context
cfg := config.New() cfg := config.New()
err := cfg.Load() err = cfg.Load()
if err != nil { if err != nil {
slog.Error(gotext.Get("Error loading config"), "err", err) slog.Error(gotext.Get("Error loading config"), "err", err)
os.Exit(1) os.Exit(1)
@@ -71,7 +78,14 @@ func UpgradeCmd() *cli.Command {
os.Exit(1) os.Exit(1)
} }
slog.Debug("builder setup")
builder := build.NewMainBuilder(
cfg,
rs,
)
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
slog.Debug("ParseOSRelease", "err", err)
if err != nil { if err != nil {
slog.Error(gotext.Get("Error parsing os-release file"), "err", err) slog.Error(gotext.Get("Error parsing os-release file"), "err", err)
os.Exit(1) os.Exit(1)
@@ -84,6 +98,7 @@ func UpgradeCmd() *cli.Command {
} }
if cfg.AutoPull() { if cfg.AutoPull() {
slog.Debug("autopull")
err = rs.Pull(ctx, cfg.Repos()) err = rs.Pull(ctx, cfg.Repos())
if err != nil { if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err) slog.Error(gotext.Get("Error pulling repos"), "err", err)
@@ -98,22 +113,22 @@ func UpgradeCmd() *cli.Command {
} }
if len(updates) > 0 { if len(updates) > 0 {
builder := build.NewBuilder( err = builder.InstallALRPackages(
ctx, ctx,
types.BuildOpts{ &build.BuildArgs{
Manager: mgr, Opts: &types.BuildOpts{
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
}, },
rs, Info: info,
info, PkgFormat_: build.GetPkgFormat(mgr),
cfg, },
updates,
) )
builder.InstallPkgs(ctx, updates, nil, types.BuildOpts{ if err != nil {
Manager: mgr, slog.Error(gotext.Get("Error checking for updates"), "err", err)
Clean: c.Bool("clean"), os.Exit(1)
Interactive: c.Bool("interactive"), }
})
} else { } else {
slog.Info(gotext.Get("There is nothing to do.")) slog.Info(gotext.Get("There is nothing to do."))
} }