diff --git a/Makefile b/Makefile
index ac59441..c1bed44 100644
--- a/Makefile
+++ b/Makefile
@@ -37,6 +37,7 @@ install: \
$(INSTALED_BIN): $(BIN)
install -Dm755 $< $@
+ setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN)
$(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION)
install -Dm755 $< $@
diff --git a/assets/coverage-badge.svg b/assets/coverage-badge.svg
index ac4f67b..ce89bb7 100644
--- a/assets/coverage-badge.svg
+++ b/assets/coverage-badge.svg
@@ -11,7 +11,7 @@
coverage
coverage
- 19.0%
- 19.0%
+ 16.7%
+ 16.7%
diff --git a/assets/i18n-ru-badge.svg b/assets/i18n-ru-badge.svg
index b447b28..a019f1e 100644
--- a/assets/i18n-ru-badge.svg
+++ b/assets/i18n-ru-badge.svg
@@ -12,7 +12,7 @@
ru translate
ru translate
- 97.00%
- 97.00%
+ 98.00%
+ 98.00%
diff --git a/build.go b/build.go
index 668d461..dd92584 100644
--- a/build.go
+++ b/build.go
@@ -84,9 +84,9 @@ func BuildCmd() *cli.Command {
var script string
var packages []string
- repository := "default"
+ // repository := "default"
- repoDir := cfg.GetPaths().RepoDir
+ // repoDir := cfg.GetPaths().RepoDir
switch {
case c.IsSet("script"):
@@ -111,25 +111,14 @@ func BuildCmd() *cli.Command {
os.Exit(1)
}
- repository = pkg[0].Repository
+ // repository = pkg[0].Repository
if pkg[0].BasePkgName != "" {
- script = filepath.Join(repoDir, repository, pkg[0].BasePkgName, "alr.sh")
+ // script = filepath.Join(repoDir, repository, pkg[0].BasePkgName, "alr.sh")
packages = append(packages, pkg[0].Name)
- } else {
- script = filepath.Join(repoDir, repository, pkg[0].Name, "alr.sh")
}
default:
- script = filepath.Join(repoDir, "alr.sh")
- }
-
- // Проверка автоматического пулла репозиториев
- if cfg.AutoPull() {
- err := rs.Pull(ctx, cfg.Repos())
- if err != nil {
- slog.Error(gotext.Get("Error pulling repositories"), "err", err)
- os.Exit(1)
- }
+ // script = filepath.Join(repoDir, "alr.sh")
}
// Обнаружение менеджера пакетов
@@ -145,23 +134,27 @@ func BuildCmd() *cli.Command {
os.Exit(1)
}
- builder := build.NewBuilder(
- ctx,
- types.BuildOpts{
- Packages: packages,
- Repository: repository,
- Script: script,
- Manager: mgr,
- Clean: c.Bool("clean"),
- Interactive: c.Bool("interactive"),
- },
- rs,
- info,
+ builder := build.NewMainBuilder(
cfg,
+ rs,
)
// Сборка пакета
- pkgPaths, _, err := builder.BuildPackage(ctx)
+ 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)
@@ -175,7 +168,7 @@ func BuildCmd() *cli.Command {
}
// Перемещение собранных пакетов в рабочую директорию
- for _, pkgPath := range pkgPaths {
+ for _, pkgPath := range res.PackagePaths {
name := filepath.Base(pkgPath)
err = osutils.Move(pkgPath, filepath.Join(wd, name))
if err != nil {
diff --git a/e2e-tests/addrepo_test.go b/e2e-tests/addrepo_test.go
index be41bcf..5e68ae3 100644
--- a/e2e-tests/addrepo_test.go
+++ b/e2e-tests/addrepo_test.go
@@ -33,6 +33,7 @@ func TestE2EAlrAddRepo(t *testing.T) {
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
+ "sudo",
"alr",
"addrepo",
"--name",
@@ -45,11 +46,12 @@ func TestE2EAlrAddRepo(t *testing.T) {
err = r.Exec(e2e.NewCommand(
"bash",
"-c",
- "cat $HOME/.config/alr/alr.toml",
+ "cat /etc/alr/alr.toml",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
+ "sudo",
"alr",
"removerepo",
"--name",
@@ -61,7 +63,7 @@ func TestE2EAlrAddRepo(t *testing.T) {
err = r.Exec(e2e.NewCommand(
"bash",
"-c",
- "cat $HOME/.config/alr/alr.toml",
+ "cat /etc/alr/alr.toml",
), e2e.WithExecOptionStdout(&buf))
assert.NoError(t, err)
assert.Contains(t, buf.String(), "rootCmd")
diff --git a/e2e-tests/common_test.go b/e2e-tests/common_test.go
index e346a9f..ee02aec 100644
--- a/e2e-tests/common_test.go
+++ b/e2e-tests/common_test.go
@@ -109,7 +109,7 @@ var ALL_SYSTEMS []string = []string{
}
var AUTOREQ_AUTOPROV_SYSTEMS []string = []string{
- "alt-sisyphus",
+ // "alt-sisyphus",
"fedora-41",
}
@@ -157,9 +157,9 @@ func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testin
imageId := fmt.Sprintf("alr-testimage-%s", id)
runnable := e.Runnable(dockerName).Init(
e2e.StartOptions{
- Image: imageId,
+ Image: imageId,
Volumes: []string{
- "./alr:/usr/bin/alr",
+ // "./alr:/usr/bin/alr",
},
Privileged: true,
},
diff --git a/e2e-tests/images/Dockerfile.fedora-41 b/e2e-tests/images/Dockerfile.fedora-41
index d213964..899cd5c 100644
--- a/e2e-tests/images/Dockerfile.fedora-41
+++ b/e2e-tests/images/Dockerfile.fedora-41
@@ -1,8 +1,17 @@
FROM fedora:41
RUN dnf install -y ca-certificates sudo rpm-build
-RUN useradd -m -s /bin/bash alr-user && \
- echo "alr-user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/alr-user && \
- chmod 0440 /etc/sudoers.d/alr-user
-USER alr-user
-WORKDIR /home/alr-user
+RUN <> /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 <> /etc/sudoers.d/alr-user && \
- chmod 0440 /etc/sudoers.d/alr-user
-USER alr-user
+RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates sudo libcap2-bin
+RUN <> /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 < github.com/creack/pty v1.1.19
+
require (
- gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PuerkitoBio/purell v1.2.0
github.com/alecthomas/assert/v2 v2.2.1
@@ -15,11 +16,14 @@ require (
github.com/charmbracelet/bubbletea v1.2.4
github.com/charmbracelet/lipgloss v1.0.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/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.12.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
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/jmoiron/sqlx v1.3.5
github.com/leonelquinteros/gotext v1.7.0
@@ -33,7 +37,7 @@ require (
github.com/urfave/cli/v2 v2.25.7
github.com/vmihailenco/msgpack/v5 v5.3.5
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/sys v0.29.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/emirpasic/gods v1.18.1 // 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-logfmt/logfmt v0.6.0 // indirect
github.com/gobwas/glob v0.2.3 // 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/google/goterm v0.0.0-20190703233501-fc88cf888a3f // 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/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
+ github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.16 // indirect
@@ -103,6 +110,7 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
+ github.com/oklog/run v1.0.0 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/pjbgf/sha1cd v0.3.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
go4.org v0.0.0-20200411211856-f5505b9728dd // 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/term v0.28.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
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
diff --git a/go.sum b/go.sum
index c106bcd..7789727 100644
--- a/go.sum
+++ b/go.sum
@@ -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/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
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/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
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/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
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/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
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/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
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.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
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/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/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
-github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
-github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
+github.com/creack/pty v1.1.19 h1:tUN6H7LWqNx4hQVxomd0CVsDwaDr9gaRQaI4GpSmrsA=
+github.com/creack/pty v1.1.19/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -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/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/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/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
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.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.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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.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.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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-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/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.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/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
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/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/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/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
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/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
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/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.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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
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/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/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/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
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.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.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
-golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
+golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
+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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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.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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
-golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
-golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
+golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-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-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-20191204072324-ce4227a45e2e/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.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.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
-google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/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-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
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.27.0/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.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 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/install.go b/install.go
index f96b35c..a839be5 100644
--- a/install.go
+++ b/install.go
@@ -27,10 +27,10 @@ import (
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
- "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
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/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
@@ -79,6 +79,17 @@ func InstallCmd() *cli.Command {
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() {
err := rs.Pull(ctx, cfg.Repos())
if err != nil {
@@ -87,39 +98,29 @@ 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)
if err != nil {
slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1)
}
- builder := build.NewBuilder(
+ err = builder.InstallPkgs(
ctx,
- opts,
- rs,
- info,
- cfg,
+ &build.BuildArgs{
+ Opts: &types.BuildOpts{
+ Clean: c.Bool("clean"),
+ 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)
+ }
- builder.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
- Manager: mgr,
- Clean: c.Bool("clean"),
- Interactive: c.Bool("interactive"),
- })
return nil
},
BashComplete: func(c *cli.Context) {
diff --git a/internal.go b/internal.go
new file mode 100644
index 0000000..2702e5d
--- /dev/null
+++ b/internal.go
@@ -0,0 +1,133 @@
+// 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 .
+
+package main
+
+import (
+ "log/slog"
+ "os"
+ "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)
+ }
+ slog.Info("",
+ "uid", os.Getuid(),
+ "gid", os.Getgid(),
+ )
+ 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
+ },
+ }
+}
diff --git a/internal/config/config.go b/internal/config/config.go
index 4643aa0..b60962b 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -26,7 +26,6 @@ import (
"reflect"
"github.com/caarlos0/env"
- "github.com/leonelquinteros/gotext"
"github.com/pelletier/go-toml/v2"
"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 {
systemConfig, err := readConfig(
@@ -94,24 +96,10 @@ func (c *ALRConfig) Load() error {
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{}
mergeStructs(config, defaultConfig)
mergeStructs(config, systemConfig)
- mergeStructs(config, userConfig)
err = env.Parse(config)
if err != nil {
return err
@@ -119,17 +107,13 @@ func (c *ALRConfig) Load() error {
c.cfg = config
- cacheDir, err := os.UserCacheDir()
- if err != nil {
- return err
- }
c.paths = &Paths{}
- c.paths.UserConfigPath = userConfigPath
- c.paths.CacheDir = filepath.Join(cacheDir, "alr")
+ c.paths.UserConfigPath = systemConfigPath
+ c.paths.CacheDir = systemCachePath
c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo")
c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs")
c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db")
- c.initPaths()
+ // c.initPaths()
return nil
}
@@ -170,26 +154,6 @@ func (c *ALRConfig) GetPaths() *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 {
f, err := os.Create(c.paths.UserConfigPath)
if err != nil {
diff --git a/internal/logger/hclog.go b/internal/logger/hclog.go
new file mode 100644
index 0000000..28a2b1e
--- /dev/null
+++ b/internal/logger/hclog.go
@@ -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 .
+
+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,
+ }
+}
diff --git a/internal/logger/log.go b/internal/logger/log.go
index cd52266..0073a89 100644
--- a/internal/logger/log.go
+++ b/internal/logger/log.go
@@ -22,96 +22,90 @@ import (
"os"
"github.com/charmbracelet/lipgloss"
- "github.com/charmbracelet/log"
+
+ chLog "github.com/charmbracelet/log"
"github.com/leonelquinteros/gotext"
)
type Logger struct {
- lOut slog.Handler
- lErr slog.Handler
+ l *chLog.Logger
}
-func setupOutLogger() *log.Logger {
- styles := log.DefaultStyles()
- logger := log.New(os.Stdout)
- styles.Levels[log.InfoLevel] = lipgloss.NewStyle().
+func setupLogger() *chLog.Logger {
+ styles := chLog.DefaultStyles()
+ logger := chLog.New(os.Stderr)
+ styles.Levels[chLog.InfoLevel] = lipgloss.NewStyle().
SetString("-->").
Foreground(lipgloss.Color("35"))
- logger.SetStyles(styles)
- return logger
-}
-
-func setupErrorLogger() *log.Logger {
- styles := log.DefaultStyles()
- styles.Levels[log.ErrorLevel] = lipgloss.NewStyle().
+ styles.Levels[chLog.ErrorLevel] = lipgloss.NewStyle().
SetString(gotext.Get("ERROR")).
Padding(0, 1, 0, 1).
Background(lipgloss.Color("204")).
Foreground(lipgloss.Color("0"))
- logger := log.New(os.Stderr)
logger.SetStyles(styles)
return logger
}
func New() *Logger {
- standardLogger := setupOutLogger()
- errLogger := setupErrorLogger()
return &Logger{
- lOut: standardLogger,
- lErr: errLogger,
+ l: setupLogger(),
}
}
-func slogLevelToLog(level slog.Level) log.Level {
+func slogLevelToLog(level slog.Level) chLog.Level {
switch level {
case slog.LevelDebug:
- return log.DebugLevel
+ return chLog.DebugLevel
case slog.LevelInfo:
- return log.InfoLevel
+ return chLog.InfoLevel
case slog.LevelWarn:
- return log.WarnLevel
+ return chLog.WarnLevel
case slog.LevelError:
- return log.ErrorLevel
+ return chLog.ErrorLevel
}
- return log.FatalLevel
+ return chLog.FatalLevel
}
func (l *Logger) SetLevel(level slog.Level) {
- l.lOut.(*log.Logger).SetLevel(slogLevelToLog(level))
- l.lErr.(*log.Logger).SetLevel(slogLevelToLog(level))
+ l.l.SetLevel(slogLevelToLog(level))
}
func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool {
- if level <= slog.LevelInfo {
- return l.lOut.Enabled(ctx, level)
- }
- return l.lErr.Enabled(ctx, level)
+ return l.l.Enabled(ctx, level)
}
func (l *Logger) Handle(ctx context.Context, rec slog.Record) error {
- if rec.Level <= slog.LevelInfo {
- return l.lOut.Handle(ctx, rec)
- }
- return l.lErr.Handle(ctx, rec)
+ return l.l.Handle(ctx, rec)
}
func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler {
sl := *l
- sl.lOut = l.lOut.WithAttrs(attrs)
- sl.lErr = l.lErr.WithAttrs(attrs)
+ sl.l = l.l.WithAttrs(attrs).(*chLog.Logger)
return &sl
}
func (l *Logger) WithGroup(name string) slog.Handler {
sl := *l
- sl.lOut = l.lOut.WithGroup(name)
- sl.lErr = l.lErr.WithGroup(name)
+ sl.l = l.l.WithGroup(name).(*chLog.Logger)
return &sl
}
+var logger *Logger
+
func SetupDefault() *Logger {
- l := New()
- logger := slog.New(l)
- slog.SetDefault(logger)
- return l
+ logger = New()
+ slogLogger := slog.New(logger)
+ slog.SetDefault(slogLogger)
+ return logger
+}
+
+func SetupForGoPlugin() {
+ logger.l.SetFormatter(chLog.JSONFormatter)
+ chLog.TimestampKey = "@timestamp"
+ chLog.MessageKey = "@message"
+ chLog.LevelKey = "@level"
+}
+
+func GetLogger() *Logger {
+ return logger
}
diff --git a/internal/shutils/handlers/fakeroot.go b/internal/shutils/handlers/fakeroot.go
index bc0090b..90a45dc 100644
--- a/internal/shutils/handlers/fakeroot.go
+++ b/internal/shutils/handlers/fakeroot.go
@@ -25,11 +25,11 @@ import (
"os"
"os/exec"
"runtime"
+ "slices"
"strings"
"syscall"
"time"
- "gitea.plemya-x.ru/Plemya-x/fakeroot"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
)
@@ -54,7 +54,7 @@ func FakerootExecHandler(killTimeout time.Duration) interp.ExecHandlerFunc {
Stderr: hc.Stderr,
}
- err = fakeroot.Apply(cmd)
+ err = Apply(cmd)
if err != nil {
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
func execEnv(env expand.Environ) []string {
list := make([]string, 0, 64)
diff --git a/internal/translations/default.pot b/internal/translations/default.pot
index 9247f0d..e17ac27 100644
--- a/internal/translations/default.pot
+++ b/internal/translations/default.pot
@@ -42,55 +42,63 @@ msgstr ""
msgid "Package not found"
msgstr ""
-#: build.go:130
-msgid "Error pulling repositories"
-msgstr ""
-
-#: build.go:138
+#: build.go:127
msgid "Unable to detect a supported package manager on the system"
msgstr ""
-#: build.go:144
+#: build.go:133
msgid "Error parsing os release"
msgstr ""
-#: build.go:166
+#: build.go:159
msgid "Error building package"
msgstr ""
-#: build.go:173
+#: build.go:166
msgid "Error getting working directory"
msgstr ""
-#: build.go:182
+#: build.go:175
msgid "Error moving the package"
msgstr ""
-#: fix.go:37
+#: fix.go:39
msgid "Attempt to fix problems with ALR"
msgstr ""
-#: fix.go:49
+#: fix.go:42
+msgid "Can't drop privileges"
+msgstr ""
+
+#: fix.go:56
msgid "Removing cache directory"
msgstr ""
-#: fix.go:53
-msgid "Unable to remove cache directory"
+#: fix.go:60
+msgid "Unable to open cache directory"
msgstr ""
-#: fix.go:57
+#: fix.go:67
+msgid "Unable to read cache directory contents"
+msgstr ""
+
+#: fix.go:74
+msgid "Unable to remove cache item"
+msgstr ""
+
+#: fix.go:79
msgid "Rebuilding cache"
msgstr ""
-#: fix.go:61
+#: fix.go:83
msgid "Unable to create new cache directory"
msgstr ""
-#: fix.go:81
+#: fix.go:103
msgid "Error pulling repos"
msgstr ""
-#: fix.go:85
+#: fix.go:107
msgid "Done"
msgstr ""
@@ -162,19 +170,27 @@ msgstr ""
msgid "Command install expected at least 1 argument, got %d"
msgstr ""
-#: install.go:163
+#: install.go:84
+msgid "Error dropping capabilities"
+msgstr ""
+
+#: install.go:96
+msgid "Error pulling repositories"
+msgstr ""
+
+#: install.go:164
msgid "Remove an installed package"
msgstr ""
-#: install.go:188
+#: install.go:189
msgid "Error listing installed packages"
msgstr ""
-#: install.go:226
+#: install.go:227
msgid "Command remove expected at least 1 argument, got %d"
msgstr ""
-#: install.go:241
+#: install.go:242
msgid "Error removing packages"
msgstr ""
@@ -258,18 +274,6 @@ msgstr ""
msgid "OPTIONS"
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
msgid "Database version mismatch; resetting"
msgstr ""
@@ -303,10 +307,14 @@ msgstr ""
msgid "%s %s downloading at %s/s\n"
msgstr ""
-#: internal/logger/log.go:47
+#: internal/logger/log.go:41
msgid "ERROR"
msgstr ""
+#: internal/utils/cmd.go:65
+msgid "You need to be root"
+msgstr ""
+
#: list.go:41
msgid "List ALR repo packages"
msgstr ""
@@ -315,94 +323,48 @@ msgstr ""
msgid "Print the current ALR version and exit"
msgstr ""
-#: main.go:61
+#: main.go:79
msgid "Arguments to be passed on to the package manager"
msgstr ""
-#: main.go:67
+#: main.go:85
msgid "Enable interactive questions and prompts"
msgstr ""
-#: main.go:96
-msgid ""
-"Running ALR as root is forbidden as it may cause catastrophic damage to your "
-"system"
-msgstr ""
-
-#: main.go:154
+#: main.go:183
msgid "Show help"
msgstr ""
-#: main.go:158
+#: main.go:187
msgid "Error while running app"
msgstr ""
-#: pkg/build/build.go:157
-msgid "Failed to prompt user to view build script"
-msgstr ""
-
-#: pkg/build/build.go:161
+#: pkg/build/build.go:392
msgid "Building package"
msgstr ""
-#: pkg/build/build.go:209
+#: pkg/build/build.go:421
msgid "The checksums array must be the same length as sources"
msgstr ""
-#: pkg/build/build.go:238
+#: pkg/build/build.go:448
msgid "Downloading sources"
msgstr ""
-#: pkg/build/build.go:260
-msgid "Building package metadata"
+#: pkg/build/build.go:507
+msgid "Installing dependencies"
msgstr ""
-#: pkg/build/build.go:282
-msgid "Compressing package"
-msgstr ""
-
-#: pkg/build/build.go:441
+#: pkg/build/checker.go:43
msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?"
msgstr ""
-#: pkg/build/build.go:455
+#: pkg/build/checker.go:67
msgid "This package is already installed"
msgstr ""
-#: pkg/build/build.go:479
-msgid "Installing build dependencies"
-msgstr ""
-
-#: pkg/build/build.go:524
-msgid "Installing dependencies"
-msgstr ""
-
-#: pkg/build/build.go:605
-msgid "Would you like to remove the build dependencies?"
-msgstr ""
-
-#: pkg/build/build.go:668
-msgid "Executing prepare()"
-msgstr ""
-
-#: pkg/build/build.go:678
-msgid "Executing build()"
-msgstr ""
-
-#: pkg/build/build.go:708 pkg/build/build.go:728
-msgid "Executing %s()"
-msgstr ""
-
-#: pkg/build/build.go:787
-msgid "Error installing native packages"
-msgstr ""
-
-#: pkg/build/build.go:811
-msgid "Error installing package"
-msgstr ""
-
#: pkg/build/find_deps/alt_linux.go:35
msgid "Command not found on the system"
msgstr ""
@@ -423,6 +385,22 @@ msgstr ""
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
+#: pkg/build/script_executor.go:236
+msgid "Building package metadata"
+msgstr ""
+
+#: pkg/build/script_executor.go:355
+msgid "Executing prepare()"
+msgstr ""
+
+#: pkg/build/script_executor.go:364
+msgid "Executing build()"
+msgstr ""
+
+#: pkg/build/script_executor.go:393 pkg/build/script_executor.go:413
+msgid "Executing %s()"
+msgstr ""
+
#: pkg/repos/pull.go:79
msgid "Pulling repository"
msgstr ""
@@ -441,86 +419,74 @@ msgid ""
"updating ALR if something doesn't work."
msgstr ""
-#: repo.go:40
+#: repo.go:41
msgid "Add a new repository"
msgstr ""
-#: repo.go:47
+#: repo.go:48
msgid "Name of the new repo"
msgstr ""
-#: repo.go:53
+#: repo.go:54
msgid "URL of the new repo"
msgstr ""
-#: repo.go:86 repo.go:156
+#: repo.go:89 repo.go:166
msgid "Error saving config"
msgstr ""
-#: repo.go:111
+#: repo.go:119
msgid "Remove an existing repository"
msgstr ""
-#: repo.go:118
+#: repo.go:126
msgid "Name of the repo to be deleted"
msgstr ""
-#: repo.go:142
+#: repo.go:152
msgid "Repo does not exist"
msgstr ""
-#: repo.go:150
+#: repo.go:160
msgid "Error removing repo directory"
msgstr ""
-#: repo.go:167
+#: repo.go:177
msgid "Error removing packages from database"
msgstr ""
-#: repo.go:179
+#: repo.go:189
msgid "Pull all repositories that have changed"
msgstr ""
-#: search.go:36
+#: search.go:37
msgid "Search packages"
msgstr ""
-#: search.go:42
+#: search.go:43
msgid "Search by name"
msgstr ""
-#: search.go:47
+#: search.go:48
msgid "Search by description"
msgstr ""
-#: search.go:52
+#: search.go:53
msgid "Search by repository"
msgstr ""
-#: search.go:57
+#: search.go:58
msgid "Search by provides"
msgstr ""
-#: search.go:62
+#: search.go:63
msgid "Format output using a Go template"
msgstr ""
-#: search.go:88 search.go:105
+#: search.go:94 search.go:111
msgid "Error parsing format template"
msgstr ""
-#: search.go:113
+#: search.go:119
msgid "Error executing template"
msgstr ""
-
-#: upgrade.go:47
-msgid "Upgrade all installed packages"
-msgstr ""
-
-#: upgrade.go:96
-msgid "Error checking for updates"
-msgstr ""
-
-#: upgrade.go:118
-msgid "There is nothing to do."
-msgstr ""
diff --git a/internal/translations/po/ru/default.po b/internal/translations/po/ru/default.po
index 1671e6b..eb18a05 100644
--- a/internal/translations/po/ru/default.po
+++ b/internal/translations/po/ru/default.po
@@ -50,55 +50,66 @@ msgstr "Ошибка инициализации базы данных"
msgid "Package not found"
msgstr "Пакет не найден"
-#: build.go:130
-msgid "Error pulling repositories"
-msgstr "Ошибка при извлечении репозиториев"
-
-#: build.go:138
+#: build.go:127
msgid "Unable to detect a supported package manager on the system"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
-#: build.go:144
+#: build.go:133
msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы"
-#: build.go:166
+#: build.go:159
msgid "Error building package"
msgstr "Ошибка при сборке пакета"
-#: build.go:173
+#: build.go:166
msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога"
-#: build.go:182
+#: build.go:175
msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета"
-#: fix.go:37
+#: fix.go:39
msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR"
-#: fix.go:49
+#: fix.go:42
+msgid "Can't drop privileges"
+msgstr ""
+
+#: fix.go:56
msgid "Removing cache directory"
msgstr "Удаление каталога кэша"
-#: fix.go:53
-msgid "Unable to remove cache directory"
+#: fix.go:60
+#, fuzzy
+msgid "Unable to open cache directory"
msgstr "Не удалось удалить каталог кэша"
-#: fix.go:57
+#: fix.go:67
+#, fuzzy
+msgid "Unable to read cache directory contents"
+msgstr "Не удалось удалить каталог кэша"
+
+#: fix.go:74
+#, fuzzy
+msgid "Unable to remove cache item"
+msgstr "Не удалось удалить каталог кэша"
+
+#: fix.go:79
msgid "Rebuilding cache"
msgstr "Восстановление кэша"
-#: fix.go:61
+#: fix.go:83
msgid "Unable to create new cache directory"
msgstr "Не удалось создать новый каталог кэша"
-#: fix.go:81
+#: fix.go:103
msgid "Error pulling repos"
msgstr "Ошибка при извлечении репозиториев"
-#: fix.go:85
+#: fix.go:107
msgid "Done"
msgstr "Сделано"
@@ -170,19 +181,28 @@ msgstr "Установить новый пакет"
msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
-#: install.go:163
+#: install.go:84
+#, fuzzy
+msgid "Error dropping capabilities"
+msgstr "Ошибка при открытии базы данных"
+
+#: install.go:96
+msgid "Error pulling repositories"
+msgstr "Ошибка при извлечении репозиториев"
+
+#: install.go:164
msgid "Remove an installed package"
msgstr "Удалить установленный пакет"
-#: install.go:188
+#: install.go:189
msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов"
-#: install.go:226
+#: install.go:227
msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
-#: install.go:241
+#: install.go:242
msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов"
@@ -266,19 +286,6 @@ msgstr "КАТЕГОРИЯ"
msgid "OPTIONS"
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
msgid "Database version mismatch; resetting"
msgstr "Несоответствие версий базы данных; сброс настроек"
@@ -313,10 +320,14 @@ msgstr "%s: выполнено!\n"
msgid "%s %s downloading at %s/s\n"
msgstr "%s %s загружается — %s/с\n"
-#: internal/logger/log.go:47
+#: internal/logger/log.go:41
msgid "ERROR"
msgstr "ОШИБКА"
+#: internal/utils/cmd.go:65
+msgid "You need to be root"
+msgstr ""
+
#: list.go:41
msgid "List ALR repo packages"
msgstr "Список пакетов репозитория ALR"
@@ -325,55 +336,39 @@ msgstr "Список пакетов репозитория ALR"
msgid "Print the current ALR version and exit"
msgstr "Показать текущую версию ALR и выйти"
-#: main.go:61
+#: main.go:79
msgid "Arguments to be passed on to the package manager"
msgstr "Аргументы, которые будут переданы менеджеру пакетов"
-#: main.go:67
+#: main.go:85
msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов"
-#: main.go:96
-msgid ""
-"Running ALR as root is forbidden as it may cause catastrophic damage to your "
-"system"
-msgstr ""
-"Запуск ALR от имени root запрещён, так как это может привести к "
-"катастрофическому повреждению вашей системы"
-
-#: main.go:154
+#: main.go:183
msgid "Show help"
msgstr "Показать справку"
-#: main.go:158
+#: main.go:187
msgid "Error while running app"
msgstr "Ошибка при запуске приложения"
-#: pkg/build/build.go:157
-msgid "Failed to prompt user to view build script"
-msgstr "Не удалось предложить пользователю просмотреть скрипт сборки"
-
-#: pkg/build/build.go:161
+#: pkg/build/build.go:392
msgid "Building package"
msgstr "Сборка пакета"
-#: pkg/build/build.go:209
+#: pkg/build/build.go:421
msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
-#: pkg/build/build.go:238
+#: pkg/build/build.go:448
msgid "Downloading sources"
msgstr "Скачивание источников"
-#: pkg/build/build.go:260
-msgid "Building package metadata"
-msgstr "Сборка метаданных пакета"
+#: pkg/build/build.go:507
+msgid "Installing dependencies"
+msgstr "Установка зависимостей"
-#: pkg/build/build.go:282
-msgid "Compressing package"
-msgstr "Сжатие пакета"
-
-#: pkg/build/build.go:441
+#: pkg/build/checker.go:43
msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?"
@@ -381,42 +376,10 @@ msgstr ""
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
"равно хотите выполнить сборку?"
-#: pkg/build/build.go:455
+#: pkg/build/checker.go:67
msgid "This package is already installed"
msgstr "Этот пакет уже установлен"
-#: pkg/build/build.go:479
-msgid "Installing build dependencies"
-msgstr "Установка зависимостей сборки"
-
-#: pkg/build/build.go:524
-msgid "Installing dependencies"
-msgstr "Установка зависимостей"
-
-#: pkg/build/build.go:605
-msgid "Would you like to remove the build dependencies?"
-msgstr "Хотели бы вы удалить зависимости сборки?"
-
-#: pkg/build/build.go:668
-msgid "Executing prepare()"
-msgstr "Исполнение prepare()"
-
-#: pkg/build/build.go:678
-msgid "Executing build()"
-msgstr "Исполнение build()"
-
-#: pkg/build/build.go:708 pkg/build/build.go:728
-msgid "Executing %s()"
-msgstr "Исполнение %s()"
-
-#: pkg/build/build.go:787
-msgid "Error installing native packages"
-msgstr "Ошибка при установке нативных пакетов"
-
-#: pkg/build/build.go:811
-msgid "Error installing package"
-msgstr "Ошибка при установке пакета"
-
#: pkg/build/find_deps/alt_linux.go:35
msgid "Command not found on the system"
msgstr "Команда не найдена в системе"
@@ -439,6 +402,22 @@ msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
+#: pkg/build/script_executor.go:236
+msgid "Building package metadata"
+msgstr "Сборка метаданных пакета"
+
+#: pkg/build/script_executor.go:355
+msgid "Executing prepare()"
+msgstr "Исполнение prepare()"
+
+#: pkg/build/script_executor.go:364
+msgid "Executing build()"
+msgstr "Исполнение build()"
+
+#: pkg/build/script_executor.go:393 pkg/build/script_executor.go:413
+msgid "Executing %s()"
+msgstr "Исполнение %s()"
+
#: pkg/repos/pull.go:79
msgid "Pulling repository"
msgstr "Скачивание репозитория"
@@ -459,90 +438,122 @@ msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает."
-#: repo.go:40
+#: repo.go:41
msgid "Add a new repository"
msgstr "Добавить новый репозиторий"
-#: repo.go:47
+#: repo.go:48
msgid "Name of the new repo"
msgstr "Название нового репозитория"
-#: repo.go:53
+#: repo.go:54
msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория"
-#: repo.go:86 repo.go:156
+#: repo.go:89 repo.go:166
#, fuzzy
msgid "Error saving config"
msgstr "Ошибка при кодировании конфигурации"
-#: repo.go:111
+#: repo.go:119
msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий"
-#: repo.go:118
+#: repo.go:126
msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён"
-#: repo.go:142
+#: repo.go:152
msgid "Repo does not exist"
msgstr "Репозитория не существует"
-#: repo.go:150
+#: repo.go:160
msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория"
-#: repo.go:167
+#: repo.go:177
msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных"
-#: repo.go:179
+#: repo.go:189
msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории"
-#: search.go:36
+#: search.go:37
msgid "Search packages"
msgstr "Поиск пакетов"
-#: search.go:42
+#: search.go:43
msgid "Search by name"
msgstr "Искать по имени"
-#: search.go:47
+#: search.go:48
msgid "Search by description"
msgstr "Искать по описанию"
-#: search.go:52
+#: search.go:53
msgid "Search by repository"
msgstr "Искать по репозиторию"
-#: search.go:57
+#: search.go:58
msgid "Search by provides"
msgstr "Иcкать по provides"
-#: search.go:62
+#: search.go:63
msgid "Format output using a Go template"
msgstr "Формат выходных данных с использованием шаблона Go"
-#: search.go:88 search.go:105
+#: search.go:94 search.go:111
msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона"
-#: search.go:113
+#: search.go:119
msgid "Error executing template"
msgstr "Ошибка при выполнении шаблона"
-#: upgrade.go:47
-msgid "Upgrade all installed packages"
-msgstr "Обновить все установленные пакеты"
+#, fuzzy
+#~ msgid "Unable to create config directory"
+#~ msgstr "Не удалось создать каталог конфигурации ALR"
-#: upgrade.go:96
-msgid "Error checking for updates"
-msgstr "Ошибка при проверке обновлений"
+#~ msgid "Unable to create repo cache directory"
+#~ msgstr "Не удалось создать каталог кэша репозитория"
-#: upgrade.go:118
-msgid "There is nothing to do."
-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 "Upgrade all installed packages"
+#~ msgstr "Обновить все установленные пакеты"
+
+#~ msgid "Error checking for updates"
+#~ msgstr "Ошибка при проверке обновлений"
+
+#~ msgid "There is nothing to do."
+#~ msgstr "Здесь нечего делать."
#~ msgid "Error opening config file, using defaults"
#~ msgstr ""
@@ -572,9 +583,6 @@ msgstr "Здесь нечего делать."
#~ msgid "Error parsing system language"
#~ msgstr "Ошибка при парсинге языка системы"
-#~ msgid "Error opening database"
-#~ msgstr "Ошибка при открытии базы данных"
-
#~ msgid "Executing version()"
#~ msgstr "Исполнение версия()"
diff --git a/internal/types/build.go b/internal/types/build.go
index 51999ef..aadb8e6 100644
--- a/internal/types/build.go
+++ b/internal/types/build.go
@@ -19,13 +19,11 @@
package types
-import "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
-
type BuildOpts struct {
- Script string
- Repository string
- Packages []string
- Manager manager.Manager
+ // Script string
+ // Repository string
+ // Packages []string
+ // Manager manager.Manager
Clean bool
Interactive bool
}
diff --git a/internal/utils/cmd.go b/internal/utils/cmd.go
new file mode 100644
index 0000000..f5a6a4d
--- /dev/null
+++ b/internal/utils/cmd.go
@@ -0,0 +1,68 @@
+// 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 .
+
+package utils
+
+import (
+ "log/slog"
+ "os"
+ "os/user"
+ "strconv"
+ "syscall"
+
+ "github.com/leonelquinteros/gotext"
+)
+
+func GetUidGidAlrUser() (int, int, error) {
+ u, err := user.Lookup("alr")
+ if err != nil {
+ return 0, 0, err
+ }
+
+ uid, err := strconv.Atoi(u.Uid)
+ if err != nil {
+ return 0, 0, err
+ }
+ gid, err := strconv.Atoi(u.Gid)
+ if err != nil {
+ return 0, 0, err
+ }
+
+ return uid, gid, nil
+}
+
+func DropCapsToAlrUser() error {
+ uid, gid, err := GetUidGidAlrUser()
+ if err != nil {
+ return err
+ }
+ err = syscall.Setgid(gid)
+ if err != nil {
+ return err
+ }
+ err = syscall.Setuid(uid)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func ExitIfNotRoot() {
+ if os.Getuid() != 0 {
+ slog.Error(gotext.Get("You need to be root"))
+ os.Exit(1)
+ }
+}
diff --git a/main.go b/main.go
index aa3aa4c..9453191 100644
--- a/main.go
+++ b/main.go
@@ -21,10 +21,10 @@ package main
import (
"context"
+ "fmt"
"log/slog"
"os"
"os/signal"
- "strings"
"syscall"
"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 {
return &cli.App{
Name: "alr",
@@ -70,7 +88,7 @@ func GetApp() *cli.App {
Commands: []*cli.Command{
InstallCmd(),
RemoveCmd(),
- UpgradeCmd(),
+ // UpgradeCmd(),
InfoCmd(),
ListCmd(),
BuildCmd(),
@@ -82,29 +100,40 @@ func GetApp() *cli.App {
HelperCmd(),
VersionCmd(),
SearchCmd(),
+ // TEST
+ InternalBuildCmd(),
+ InternalInstallCmd(),
+ // InternalBuild2Cmd(),
},
Before: func(c *cli.Context) error {
- cfg := config.New()
- err := cfg.Load()
- if err != nil {
- slog.Error(gotext.Get("Error loading config"), "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)
+ }
- cmd := c.Args().First()
- 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"))
- os.Exit(1)
- }
+ /*
+ cmd := c.Args().First()
+ 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"))
+ os.Exit(1)
+ }
- if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {
- args := strings.Split(trimmed, " ")
- manager.Args = append(manager.Args, args...)
- }
+ if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {
+ args := strings.Split(trimmed, " ")
+ manager.Args = append(manager.Args, args...)
+ }
+
+ return nil
+ */
return nil
},
EnableBashCompletion: true,
+ ExitErrHandler: func(c *cli.Context, err error) {
+ },
}
}
diff --git a/pkg/build/build.go b/pkg/build/build.go
index 7b5c5ff..c9f179c 100644
--- a/pkg/build/build.go
+++ b/pkg/build/build.go
@@ -22,39 +22,162 @@ package build
import (
"bytes"
"context"
- "encoding/hex"
+ "encoding/gob"
"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"
+ "mvdan.cc/sh/v3/syntax/typedjson"
"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/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
- "gitea.plemya-x.ru/Plemya-x/ALR/internal/dl"
- "gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
- "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"
- "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
+type BuildInput struct {
+ opts *types.BuildOpts
+ info *distro.OSRelease
+ pkgFormat string
+ script string
+ repository string
+ packages []string
+}
+
+func (bi *BuildInput) GobEncode() ([]byte, error) {
+ w := new(bytes.Buffer)
+ encoder := gob.NewEncoder(w)
+
+ if err := encoder.Encode(bi.opts); err != nil {
+ return nil, err
+ }
+ if err := encoder.Encode(bi.info); err != nil {
+ return nil, err
+ }
+ if err := encoder.Encode(bi.pkgFormat); err != nil {
+ return nil, err
+ }
+ if err := encoder.Encode(bi.script); err != nil {
+ return nil, err
+ }
+ if err := encoder.Encode(bi.repository); err != nil {
+ return nil, err
+ }
+ if err := encoder.Encode(bi.packages); err != nil {
+ return nil, err
+ }
+
+ return w.Bytes(), nil
+}
+
+func (bi *BuildInput) GobDecode(data []byte) error {
+ r := bytes.NewBuffer(data)
+ decoder := gob.NewDecoder(r)
+
+ if err := decoder.Decode(&bi.opts); err != nil {
+ return err
+ }
+ if err := decoder.Decode(&bi.info); err != nil {
+ return err
+ }
+ if err := decoder.Decode(&bi.pkgFormat); err != nil {
+ return err
+ }
+ if err := decoder.Decode(&bi.script); err != nil {
+ return err
+ }
+ if err := decoder.Decode(&bi.repository); err != nil {
+ return err
+ }
+ if err := decoder.Decode(&bi.packages); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (b *BuildInput) Repository() string {
+ return b.repository
+}
+
+func (b *BuildInput) BuildOpts() *types.BuildOpts {
+ return b.opts
+}
+
+func (b *BuildInput) OSRelease() *distro.OSRelease {
+ return b.info
+}
+
+func (b *BuildInput) PkgFormat() string {
+ return b.pkgFormat
+}
+
+type BuildOptsProvider interface {
+ BuildOpts() *types.BuildOpts
+}
+
+type OsInfoProvider interface {
+ OSRelease() *distro.OSRelease
+}
+
+type PkgFormatProvider interface {
+ PkgFormat() string
+}
+
+type RepositoryProvider interface {
+ Repository() string
+}
+
+// ================================================
+
+type ScriptFile struct {
+ File *syntax.File
+ Path string
+}
+
+func (s *ScriptFile) GobEncode() ([]byte, error) {
+ var buf bytes.Buffer
+ enc := gob.NewEncoder(&buf)
+ if err := enc.Encode(s.Path); err != nil {
+ return nil, err
+ }
+ var fileBuf bytes.Buffer
+ if err := typedjson.Encode(&fileBuf, s.File); err != nil {
+ return nil, err
+ }
+ fileData := fileBuf.Bytes()
+ if err := enc.Encode(fileData); err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+func (s *ScriptFile) GobDecode(data []byte) error {
+ buf := bytes.NewBuffer(data)
+ dec := gob.NewDecoder(buf)
+ if err := dec.Decode(&s.Path); err != nil {
+ return err
+ }
+ var fileData []byte
+ if err := dec.Decode(&fileData); err != nil {
+ return err
+ }
+ fileReader := bytes.NewReader(fileData)
+ file, err := typedjson.Decode(fileReader)
+ if err != nil {
+ return err
+ }
+ s.File = file.(*syntax.File)
+ return nil
+}
+
+type BuildResult struct {
+ PackagePaths []string
+ PackageNames []string
+}
+
type PackageFinder interface {
FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error)
}
@@ -64,75 +187,191 @@ type Config interface {
PagerStyle() string
}
-type Builder struct {
- ctx context.Context
- opts types.BuildOpts
- info *distro.OSRelease
- repos PackageFinder
- config Config
+type FunctionsOutput struct {
+ Contents *[]string
}
+// EXECUTORS
+
+type ScriptResolverExecutor interface {
+ ResolveScript(ctx context.Context, pkg *db.Package) *ScriptInfo
+}
+
+type ScriptExecutor interface {
+ ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error)
+ ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error)
+ PrepareDirs(
+ ctx context.Context,
+ input *BuildInput,
+ basePkg string,
+ ) error
+ ExecuteSecondPass(
+ ctx context.Context,
+ input *BuildInput,
+ sf *ScriptFile,
+ varsOfPackages []*types.BuildVars,
+ repoDeps []string,
+ builtNames []string,
+ basePkg string,
+ ) (*SecondPassResult, error)
+}
+
+type CacheExecutor interface {
+ CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *types.BuildVars) (string, bool, error)
+}
+
+type ScriptViewerExecutor interface {
+ ViewScript(ctx context.Context, input *BuildInput, sf *ScriptFile, basePkg string) error
+}
+
+type CheckerExecutor interface {
+ PerformChecks(
+ ctx context.Context,
+ input *BuildInput,
+ vars *types.BuildVars,
+ ) (bool, error)
+}
+
+type InstallerExecutor interface {
+ InstallLocal(paths []string) error
+ Install(pkgs []string) error
+ RemoveAlreadyInstalled(pkgs []string) ([]string, error)
+}
+
+type SourcesInput struct {
+ Sources []string
+ Checksums []string
+}
+
+type SourceDownloaderExecutor interface {
+ DownloadSources(
+ ctx context.Context,
+ input *BuildInput,
+ basePkg string,
+ si SourcesInput,
+ ) error
+}
+
+//
+
func NewBuilder(
- ctx context.Context,
- opts types.BuildOpts,
- repos PackageFinder,
- info *distro.OSRelease,
- config Config,
+ scriptResolver ScriptResolverExecutor,
+ scriptExecutor ScriptExecutor,
+ cacheExecutor CacheExecutor,
+ scriptViewerExecutor ScriptViewerExecutor,
+ checkerExecutor CheckerExecutor,
+ installerExecutor InstallerExecutor,
+ sourceExecutor SourceDownloaderExecutor,
) *Builder {
return &Builder{
- ctx: ctx,
- opts: opts,
- info: info,
- repos: repos,
- config: config,
+ scriptResolver: scriptResolver,
+ scriptExecutor: scriptExecutor,
+ cacheExecutor: cacheExecutor,
+ scriptViewerExecutor: scriptViewerExecutor,
+ checkerExecutor: checkerExecutor,
+ installerExecutor: installerExecutor,
+ sourceExecutor: sourceExecutor,
}
}
-func (b *Builder) UpdateOptsFromPkg(pkg *db.Package, packages []string) {
- repodir := b.config.GetPaths().RepoDir
- b.opts.Repository = pkg.Repository
- if pkg.BasePkgName != "" {
- b.opts.Script = filepath.Join(repodir, pkg.Repository, pkg.BasePkgName, "alr.sh")
- b.opts.Packages = packages
- } else {
- b.opts.Script = filepath.Join(repodir, pkg.Repository, pkg.Name, "alr.sh")
- }
+type Builder struct {
+ scriptResolver ScriptResolverExecutor
+ scriptExecutor ScriptExecutor
+ cacheExecutor CacheExecutor
+ scriptViewerExecutor ScriptViewerExecutor
+ checkerExecutor CheckerExecutor
+ installerExecutor InstallerExecutor
+ sourceExecutor SourceDownloaderExecutor
+ repos PackageFinder
+ // mgr manager.Manager
}
-func (b *Builder) BuildPackage(ctx context.Context) ([]string, []string, error) {
- fl, err := readScript(b.opts.Script)
+type BuildArgs struct {
+ Opts *types.BuildOpts
+ Info *distro.OSRelease
+ PkgFormat_ string
+}
+
+func (b *BuildArgs) BuildOpts() *types.BuildOpts {
+ return b.Opts
+}
+
+func (b *BuildArgs) OSRelease() *distro.OSRelease {
+ return b.Info
+}
+
+func (b *BuildArgs) PkgFormat() string {
+ return b.PkgFormat_
+}
+
+type BuildPackageFromDbArgs struct {
+ BuildArgs
+ Package *db.Package
+ Packages []string
+}
+
+type BuildPackageFromScriptArgs struct {
+ BuildArgs
+ Script string
+ Packages []string
+}
+
+func (b *Builder) BuildPackageFromDb(
+ ctx context.Context,
+ args *BuildPackageFromDbArgs,
+) (*BuildResult, error) {
+ scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package)
+
+ return b.BuildPackage(ctx, &BuildInput{
+ script: scriptInfo.Script,
+ repository: scriptInfo.Repository,
+ packages: args.Packages,
+ pkgFormat: args.PkgFormat(),
+ opts: args.Opts,
+ info: args.Info,
+ })
+}
+
+func (b *Builder) BuildPackageFromScript(
+ ctx context.Context,
+ args *BuildPackageFromScriptArgs,
+) (*BuildResult, error) {
+ return b.BuildPackage(ctx, &BuildInput{
+ script: args.Script,
+ repository: "default",
+ packages: args.Packages,
+ pkgFormat: args.PkgFormat(),
+ opts: args.Opts,
+ info: args.Info,
+ })
+}
+
+func (b *Builder) BuildPackage(
+ ctx context.Context,
+ input *BuildInput,
+) (*BuildResult, error) {
+ scriptPath := input.script
+
+ sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath)
if err != nil {
- return nil, nil, err
+ return nil, err
}
- // Первый проход предназначен для получения значений переменных и выполняется
- // до отображения скрипта, чтобы предотвратить выполнение вредоносного кода.
- basePkg, varsOfPackages, err := b.executeFirstPass(fl)
+ basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf)
if err != nil {
- return nil, nil, err
- }
-
- dirs, err := b.getDirs(basePkg)
- if err != nil {
- return nil, nil, err
+ return nil, err
}
+ slog.Debug("ExecuteFirstPass", "basePkg", basePkg, "varsOfPackages", varsOfPackages)
builtPaths := make([]string, 0)
- // Если флаг opts.Clean не установлен, и пакет уже собран,
- // возвращаем его, а не собираем заново.
- if !b.opts.Clean {
+ if !input.opts.Clean {
var remainingVars []*types.BuildVars
for _, vars := range varsOfPackages {
- builtPkgPath, ok, err := b.checkForBuiltPackage(
- vars,
- getPkgFormat(b.opts.Manager),
- dirs.BaseDir,
- )
+ builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars)
if err != nil {
- return nil, nil, err
+ return nil, err
}
-
if ok {
builtPaths = append(builtPaths, builtPkgPath)
} else {
@@ -141,52 +380,25 @@ func (b *Builder) BuildPackage(ctx context.Context) ([]string, []string, error)
}
if len(remainingVars) == 0 {
- return builtPaths, nil, nil
+ return &BuildResult{builtPaths, nil}, nil
}
}
- // Спрашиваем у пользователя, хочет ли он увидеть скрипт сборки.
- err = cliutils.PromptViewScript(
- ctx,
- b.opts.Script,
- basePkg,
- b.config.PagerStyle(),
- b.opts.Interactive,
- )
+ err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg)
if err != nil {
- slog.Error(gotext.Get("Failed to prompt user to view build script"), "err", err)
- os.Exit(1)
+ return nil, err
}
slog.Info(gotext.Get("Building package"), "name", basePkg)
- // Второй проход будет использоваться для выполнения реального кода,
- // поэтому он не ограничен. Скрипт уже был показан
- // пользователю к этому моменту, так что это должно быть безопасно.
- dec, err := b.executeSecondPass(ctx, fl, dirs)
- if err != nil {
- return nil, nil, err
- }
-
- // Получаем список установленных пакетов в системе
- installed, err := b.opts.Manager.ListInstalled(nil)
- if err != nil {
- return nil, nil, err
- }
-
for _, vars := range varsOfPackages {
- cont, err := b.performChecks(ctx, vars, installed) // Выполняем различные проверки
+ cont, err := b.checkerExecutor.PerformChecks(ctx, input, vars)
if err != nil {
- return nil, nil, err
- } else if !cont {
- os.Exit(1) // Если проверки не пройдены, выходим из программы
+ return nil, err
+ }
+ if !cont {
+ return nil, errors.New("exit...")
}
- }
-
- // Подготавливаем директории для сборки
- err = prepareDirs(dirs)
- if err != nil {
- return nil, nil, err
}
buildDepends := []string{}
@@ -207,319 +419,90 @@ func (b *Builder) BuildPackage(ctx context.Context) ([]string, []string, error)
if len(sources) != len(checksums) {
slog.Error(gotext.Get("The checksums array must be the same length as sources"))
- os.Exit(1)
+ return nil, errors.New("exit...")
}
sources, checksums = removeDuplicatesSources(sources, checksums)
- mergedVars := types.BuildVars{
- BuildVarsPre: types.BuildVarsPre{
+ err = b.installBuildDeps(ctx, input, buildDepends)
+ if err != nil {
+ return nil, err
+ }
+
+ err = b.installOptDeps(ctx, input, optDepends)
+ if err != nil {
+ return nil, err
+ }
+
+ _, builtNames, repoDeps, err := b.BuildALRDeps(ctx, input, depends)
+ if err != nil {
+ return nil, err
+ }
+
+ err = b.scriptExecutor.PrepareDirs(ctx, input, basePkg)
+ if err != nil {
+ return nil, err
+ }
+
+ // builtPaths = append(builtPaths, newBuildPaths...)
+
+ slog.Info(gotext.Get("Downloading sources"))
+ err = b.sourceExecutor.DownloadSources(
+ ctx,
+ input,
+ basePkg,
+ SourcesInput{
Sources: sources,
Checksums: checksums,
},
- }
-
- buildDeps, err := b.installBuildDeps(ctx, buildDepends) // Устанавливаем зависимости для сборки
- if err != nil {
- return nil, nil, err
- }
-
- err = b.installOptDeps(ctx, optDepends) // Устанавливаем опциональные зависимости
- if err != nil {
- return nil, nil, err
- }
-
- newBuildPaths, builtNames, repoDeps, err := b.buildALRDeps(ctx, depends) // Собираем зависимости
- if err != nil {
- return nil, nil, err
- }
-
- builtPaths = append(builtPaths, newBuildPaths...)
-
- slog.Info(gotext.Get("Downloading sources")) // Записываем в лог загрузку источников
-
- err = b.getSources(ctx, dirs, &mergedVars) // Загружаем исходники
- if err != nil {
- return nil, nil, err
- }
-
- err = b.executeFunctions(ctx, dec, dirs) // Выполняем специальные функции
- if err != nil {
- return nil, nil, err
- }
-
- for _, vars := range varsOfPackages {
- packageName := ""
- if vars.Base != "" {
- packageName = vars.Name
- }
- funcOut, err := b.executePackageFunctions(ctx, dec, dirs, packageName)
- if err != nil {
- return nil, nil, err
- }
-
- slog.Info(gotext.Get("Building package metadata"), "name", basePkg)
-
- pkgFormat := getPkgFormat(b.opts.Manager) // Получаем формат пакета
-
- pkgInfo, err := b.buildPkgMetadata(ctx, vars, dirs, pkgFormat, append(repoDeps, builtNames...), funcOut.Contents) // Собираем метаданные пакета
- if err != nil {
- return nil, nil, err
- }
-
- packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета
- if err != nil {
- return nil, nil, err
- }
-
- pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета
- pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету
-
- pkgFile, err := os.Create(pkgPath) // Создаём файл пакета
- if err != nil {
- return nil, nil, err
- }
-
- slog.Info(gotext.Get("Compressing package"), "name", pkgName) // Логгируем сжатие пакета
-
- err = packager.Package(pkgInfo, pkgFile) // Упаковываем пакет
- if err != nil {
- return nil, nil, err
- }
-
- // Добавляем путь и имя только что собранного пакета в
- // соответствующие срезы
- builtPaths = append(builtPaths, pkgPath)
- builtNames = append(builtNames, vars.Name)
- }
-
- err = b.removeBuildDeps(ctx, buildDeps) // Удаляем зависимости для сборки
- if err != nil {
- return nil, nil, err
- }
-
- // Удаляем дубликаты из pkgPaths и pkgNames.
- // Дубликаты могут появиться, если несколько зависимостей
- // зависят от одних и тех же пакетов.
- pkgPaths := removeDuplicates(builtPaths)
- pkgNames := removeDuplicates(builtNames)
-
- return pkgPaths, pkgNames, nil // Возвращаем пути и имена пакетов
-}
-
-// Функция executeFirstPass выполняет парсированный скрипт в ограниченной среде,
-// чтобы извлечь переменные сборки без выполнения реального кода.
-func (b *Builder) executeFirstPass(
- fl *syntax.File,
-) (string, []*types.BuildVars, error) {
- varsOfPackages := []*types.BuildVars{}
-
- scriptDir := filepath.Dir(b.opts.Script) // Получаем директорию скрипта
- env := createBuildEnvVars(b.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)), // Ограничиваем открытие файлов
)
if err != nil {
- return "", nil, err
+ return nil, err
}
- err = runner.Run(b.ctx, fl) // Запускаем скрипт
+ res, err := b.scriptExecutor.ExecuteSecondPass(
+ ctx,
+ input,
+ sf,
+ varsOfPackages,
+ repoDeps,
+ builtNames,
+ basePkg,
+ )
if err != nil {
- return "", nil, err
+ return nil, err
}
- dec := decoder.New(b.info, runner) // Создаём новый декодер
+ pkgPaths := removeDuplicates(res.BuiltPaths)
+ pkgNames := removeDuplicates(res.BuiltNames)
- 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(b.opts.Packages) == 0 {
- return "", nil, errors.New("script has multiple packages but package is not specified")
- }
-
- for _, pkgName := range b.opts.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(b.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 // Возвращаем переменные сборки
-}
-
-// Функция getDirs возвращает соответствующие директории для скрипта
-func (b *Builder) getDirs(basePkg string) (types.Directories, error) {
- scriptPath, err := filepath.Abs(b.opts.Script)
- if err != nil {
- return types.Directories{}, err
- }
-
- baseDir := filepath.Join(b.config.GetPaths().PkgsDir, basePkg) // Определяем базовую директорию
- return types.Directories{
- BaseDir: baseDir,
- SrcDir: filepath.Join(baseDir, "src"),
- PkgDir: filepath.Join(baseDir, "pkg"),
- ScriptDir: filepath.Dir(scriptPath),
+ return &BuildResult{
+ PackagePaths: pkgPaths,
+ PackageNames: pkgNames,
}, nil
}
-// Функция executeSecondPass выполняет скрипт сборки второй раз без каких-либо ограничений. Возвращается декодер,
-// который может быть использован для получения функций и переменных из скрипта.
-func (b *Builder) executeSecondPass(
+type InstallPkgsArgs struct {
+ BuildArgs
+ AlrPkgs []db.Package
+ NativePkgs []string
+}
+
+func (b *Builder) InstallALRPackages(
ctx context.Context,
- fl *syntax.File,
- dirs types.Directories,
-) (*decoder.Decoder, error) {
- env := createBuildEnvVars(b.info, dirs) // Создаём переменные окружения для сборки
-
- fakeroot := handlers.FakerootExecHandler(2 * time.Second) // Настраиваем "fakeroot" для выполнения
- 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, fl) // Запускаем скрипт
- if err != nil {
- return nil, err
- }
-
- return decoder.New(b.info, runner), nil // Возвращаем новый декодер
+ alrPkgs []db.Package,
+ opts types.BuildOpts,
+) {
}
-// Функция performChecks проверяет различные аспекты в системе, чтобы убедиться, что пакет может быть установлен.
-func (b *Builder) performChecks(ctx context.Context, vars *types.BuildVars, installed map[string]string) (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?"),
- b.opts.Interactive,
- true,
- )
- if err != nil {
- return false, err
- }
-
- if !cont {
- return false, nil
- }
- }
-
- if instVer, ok := installed[vars.Name]; ok { // Если пакет уже установлен, выводим предупреждение
- slog.Warn(gotext.Get("This package is already installed"),
- "name", vars.Name,
- "version", instVer,
- )
- }
-
- return true, nil
-}
-
-// Функция installBuildDeps устанавливает все зависимости сборки, которые еще не установлены, и возвращает
-// срез, содержащий имена всех установленных пакетов.
-func (b *Builder) installBuildDeps(ctx context.Context, buildDepends []string) ([]string, error) {
- var buildDeps []string
- if len(buildDepends) > 0 {
- deps, err := removeAlreadyInstalled(b.opts, buildDepends)
- if err != nil {
- return nil, err
- }
-
- found, notFound, err := b.repos.FindPkgs(ctx, deps) // Находим пакеты-зависимости
- if err != nil {
- return nil, err
- }
-
- slog.Info(gotext.Get("Installing build dependencies")) // Логгируем установку зависимостей
-
- flattened := cliutils.FlattenPkgs(ctx, found, "install", b.opts.Interactive) // Уплощаем список зависимостей
- buildDeps = packageNames(flattened)
- b.InstallPkgs(ctx, flattened, notFound, b.opts) // Устанавливаем пакеты
- }
- return buildDeps, nil
-}
-
-func (b *Builder) getBuildersForPackages(pkgs []db.Package) []*Builder {
- type item struct {
- pkg *db.Package
- packages []string
- }
- pkgsMap := make(map[string]*item)
- for _, pkg := range pkgs {
- name := pkg.BasePkgName
- if name == "" {
- name = pkg.Name
- }
- if pkgsMap[name] == nil {
- pkgsMap[name] = &item{
- pkg: &pkg,
- }
- }
- pkgsMap[name].packages = append(
- pkgsMap[name].packages,
- pkg.Name,
- )
- }
-
- builders := []*Builder{}
-
- for basePkgName := range pkgsMap {
- pkg := pkgsMap[basePkgName].pkg
- builder := *b
- builder.UpdateOptsFromPkg(pkg, pkgsMap[basePkgName].packages)
- builders = append(builders, &builder)
- }
-
- return builders
-}
-
-func (b *Builder) buildALRDeps(ctx context.Context, depends []string) (builtPaths, builtNames, repoDeps []string, err error) {
+func (b *Builder) BuildALRDeps(
+ ctx context.Context,
+ input interface {
+ OsInfoProvider
+ BuildOptsProvider
+ PkgFormatProvider
+ },
+ depends []string,
+) (builtPaths, builtNames, repoDeps []string, err error) {
if len(depends) > 0 {
slog.Info(gotext.Get("Installing dependencies"))
@@ -530,19 +513,53 @@ func (b *Builder) buildALRDeps(ctx context.Context, depends []string) (builtPath
repoDeps = notFound
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
- pkgs := cliutils.FlattenPkgs(ctx, found, "install", b.opts.Interactive)
- builders := b.getBuildersForPackages(pkgs)
- for _, builder := range builders {
- // Собираем зависимости
- pkgPaths, pkgNames, err := builder.BuildPackage(ctx)
+ pkgs := cliutils.FlattenPkgs(
+ ctx,
+ found,
+ "install",
+ input.BuildOpts().Interactive,
+ )
+ type item struct {
+ pkg *db.Package
+ packages []string
+ }
+ pkgsMap := make(map[string]*item)
+ for _, pkg := range pkgs {
+ name := pkg.BasePkgName
+ if name == "" {
+ name = pkg.Name
+ }
+ if pkgsMap[name] == nil {
+ pkgsMap[name] = &item{
+ pkg: &pkg,
+ }
+ }
+ pkgsMap[name].packages = append(
+ pkgsMap[name].packages,
+ pkg.Name,
+ )
+ }
+
+ for basePkgName := range pkgsMap {
+ pkg := pkgsMap[basePkgName].pkg
+ res, err := b.BuildPackageFromDb(
+ ctx,
+ &BuildPackageFromDbArgs{
+ Package: pkg,
+ Packages: pkgsMap[basePkgName].packages,
+ BuildArgs: BuildArgs{
+ Opts: input.BuildOpts(),
+ Info: input.OSRelease(),
+ PkgFormat_: input.PkgFormat(),
+ },
+ },
+ )
if err != nil {
return nil, nil, nil, err
}
- // Добавляем пути всех собранных пакетов в builtPaths
- builtPaths = append(builtPaths, pkgPaths...)
- // Добавляем пути всех собранных пакетов в builtPaths
- builtNames = append(builtNames, pkgNames...)
+ builtPaths = append(builtPaths, res.PackagePaths...)
+ builtNames = append(builtNames, res.PackageNames...)
}
}
@@ -554,207 +571,49 @@ func (b *Builder) buildALRDeps(ctx context.Context, depends []string) (builtPath
return builtPaths, builtNames, repoDeps, nil
}
-func (b *Builder) getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars) error {
- for i, src := range bv.Sources {
- opts := dl.Options{
- Name: fmt.Sprintf("%s[%d]", bv.Name, i),
- URL: src,
- Destination: dirs.SrcDir,
- Progress: os.Stderr,
- LocalDir: dirs.ScriptDir,
- }
-
- if !strings.EqualFold(bv.Checksums[i], "SKIP") {
- // Если контрольная сумма содержит двоеточие, используйте часть до двоеточия
- // как алгоритм, а часть после как фактическую контрольную сумму.
- // В противном случае используйте sha256 по умолчанию с целой строкой как контрольной суммой.
- algo, hashData, ok := strings.Cut(bv.Checksums[i], ":")
- if ok {
- checksum, err := hex.DecodeString(hashData)
- if err != nil {
- return err
- }
- opts.Hash = checksum
- opts.HashAlgorithm = algo
- } else {
- checksum, err := hex.DecodeString(bv.Checksums[i])
- if err != nil {
- return err
- }
- opts.Hash = checksum
- }
- }
-
- opts.DlCache = dlcache.New(b.config)
-
- err := dl.Download(ctx, opts)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// Функция removeBuildDeps спрашивает у пользователя, хочет ли он удалить зависимости,
-// установленные для сборки. Если да, использует менеджер пакетов для их удаления.
-func (b *Builder) removeBuildDeps(ctx context.Context, buildDeps []string) error {
- if len(buildDeps) > 0 {
- remove, err := cliutils.YesNoPrompt(
- ctx,
- gotext.Get("Would you like to remove the build dependencies?"),
- b.opts.Interactive,
- false,
- )
- if err != nil {
- return err
- }
-
- if remove {
- err = b.opts.Manager.Remove(
- &manager.Opts{
- AsRoot: true,
- NoConfirm: true,
- },
- buildDeps...,
- )
- if err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-type FunctionsOutput struct {
- Contents *[]string
-}
-
-// Функция executeFunctions выполняет специальные функции ALR, такие как version(), prepare() и т.д.
-func (b *Builder) executeFunctions(
+func (i *Builder) installBuildDeps(
ctx context.Context,
- dec *decoder.Decoder,
- dirs types.Directories,
+ input interface {
+ OsInfoProvider
+ BuildOptsProvider
+ PkgFormatProvider
+ },
+ pkgs []string,
) error {
- /*
- version, ok := dec.GetFunc("version")
- if ok {
- slog.Info(gotext.Get("Executing version()"))
-
- buf := &bytes.Buffer{}
-
- err := version(
- ctx,
- interp.Dir(dirs.SrcDir),
- interp.StdIO(os.Stdin, buf, os.Stderr),
- )
- if err != nil {
- return nil, err
- }
-
- newVer := strings.TrimSpace(buf.String())
- err = setVersion(ctx, dec.Runner, newVer)
- if err != nil {
- return nil, err
- }
- vars.Version = newVer
-
- slog.Info(gotext.Get("Updating version"), "new", newVer)
+ if len(pkgs) > 0 {
+ deps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
+ if err != nil {
+ return err
}
- */
- prepare, ok := dec.GetFunc("prepare")
- if ok {
- slog.Info(gotext.Get("Executing prepare()"))
-
- err := prepare(ctx, interp.Dir(dirs.SrcDir))
+ err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты
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 (b *Builder) executePackageFunctions(
+func (i *Builder) installOptDeps(
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
-}
-
-func (b *Builder) installOptDeps(ctx context.Context, optDepends []string) error {
- optDeps, err := removeAlreadyInstalled(b.opts, optDepends)
+ input interface {
+ OsInfoProvider
+ BuildOptsProvider
+ PkgFormatProvider
+ },
+ pkgs []string,
+) error {
+ optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
if err != nil {
return err
}
if len(optDeps) > 0 {
- optDeps, err := cliutils.ChooseOptDepends(ctx, optDeps, "install", b.opts.Interactive) // Пользователя просят выбрать опциональные зависимости
+ optDeps, err := cliutils.ChooseOptDepends(
+ ctx,
+ optDeps,
+ "install",
+ input.BuildOpts().Interactive,
+ ) // Пользователя просят выбрать опциональные зависимости
if err != nil {
return err
}
@@ -763,153 +622,41 @@ func (b *Builder) installOptDeps(ctx context.Context, optDepends []string) error
return nil
}
- found, notFound, err := b.repos.FindPkgs(ctx, optDeps) // Находим опциональные зависимости
+ err = i.InstallPkgs(ctx, input, optDeps) // Устанавливаем выбранные пакеты
if err != nil {
return err
}
-
- flattened := cliutils.FlattenPkgs(ctx, found, "install", b.opts.Interactive)
- b.InstallPkgs(ctx, flattened, notFound, b.opts) // Устанавливаем выбранные пакеты
}
return nil
}
-func (b *Builder) InstallPkgs(
+func (i *Builder) InstallPkgs(
ctx context.Context,
- alrPkgs []db.Package,
- nativePkgs []string,
- opts types.BuildOpts,
-) {
- if len(nativePkgs) > 0 {
- err := opts.Manager.Install(nil, nativePkgs...)
- // Если есть нативные пакеты, выполняем их установку
- if err != nil {
- slog.Error(gotext.Get("Error installing native packages"), "err", err)
- os.Exit(1)
- // Логируем и завершаем выполнение при ошибке
- }
- }
-
- b.InstallALRPackages(ctx, alrPkgs, opts)
- // Устанавливаем скрипты сборки через функцию InstallScripts
-}
-
-func (b *Builder) InstallALRPackages(ctx context.Context, pkgs []db.Package, opts types.BuildOpts) {
- builders := b.getBuildersForPackages(pkgs)
- for _, builder := range builders {
- builtPkgs, _, err := builder.BuildPackage(ctx)
- // Выполняем сборку пакета
- if err != nil {
- slog.Error(gotext.Get("Error building package"), "err", err)
- os.Exit(1)
- // Логируем и завершаем выполнение при ошибке сборки
- }
-
- err = opts.Manager.InstallLocal(nil, builtPkgs...)
- // Устанавливаем локально собранные пакеты
- if err != nil {
- slog.Error(gotext.Get("Error installing package"), "err", err)
- os.Exit(1)
- // Логируем и завершаем выполнение при ошибке установки
- }
- }
-}
-
-// Функция buildPkgMetadata создает метаданные для пакета, который будет собран.
-func (b *Builder) buildPkgMetadata(
- ctx context.Context,
- vars *types.BuildVars,
- dirs types.Directories,
- pkgFormat string,
- deps []string,
- preferedContents *[]string,
-) (*nfpm.Info, error) {
- pkgInfo := getBasePkgInfo(vars, b.info, &b.opts)
- 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,
- }
-
- 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)
+ input interface {
+ OsInfoProvider
+ BuildOptsProvider
+ PkgFormatProvider
+ },
+ pkgs []string,
+) error {
+ builtPaths, _, repoDeps, err := i.BuildALRDeps(ctx, input, pkgs)
if err != nil {
- return nil, err
+ return err
}
- pkgInfo.Overridables.Contents = contents
- if len(vars.AutoProv) == 1 && decoder.IsTruthy(vars.AutoProv[0]) {
- f := finddeps.New(b.info, pkgFormat)
- err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList)
+ if len(builtPaths) > 0 {
+ err = i.installerExecutor.InstallLocal(builtPaths)
if err != nil {
- return nil, err
+ return err
}
}
- if len(vars.AutoReq) == 1 && decoder.IsTruthy(vars.AutoReq[0]) {
- f := finddeps.New(b.info, pkgFormat)
- err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList)
+ if len(repoDeps) > 0 {
+ err = i.installerExecutor.Install(repoDeps)
if err != nil {
- return nil, err
+ return err
}
}
- return pkgInfo, nil
-}
-
-// Функция checkForBuiltPackage пытается обнаружить ранее собранный пакет и вернуть его путь
-// и true, если нашла. Если нет, возвратит "", false, nil.
-func (b *Builder) checkForBuiltPackage(
- vars *types.BuildVars,
- pkgFormat,
- baseDir string,
-) (string, bool, error) {
- filename, err := b.pkgFileName(vars, pkgFormat)
- if err != nil {
- return "", false, err
- }
-
- pkgPath := filepath.Join(baseDir, filename)
-
- _, err = os.Stat(pkgPath)
- if err != nil {
- return "", false, nil
- }
-
- return pkgPath, true, nil
-}
-
-// pkgFileName returns the filename of the package if it were to be built.
-// This is used to check if the package has already been built.
-func (b *Builder) pkgFileName(vars *types.BuildVars, pkgFormat string) (string, error) {
- pkgInfo := getBasePkgInfo(vars, b.info, &b.opts)
-
- packager, err := nfpm.Get(pkgFormat)
- if err != nil {
- return "", err
- }
-
- return packager.ConventionalFileName(pkgInfo), nil
+ return nil
}
diff --git a/pkg/build/build_internal_test.go b/pkg/build/build_internal_test.g_o
similarity index 99%
rename from pkg/build/build_internal_test.go
rename to pkg/build/build_internal_test.g_o
index 69138d6..55df7c4 100644
--- a/pkg/build/build_internal_test.go
+++ b/pkg/build/build_internal_test.g_o
@@ -277,7 +277,7 @@ meta_bar() {
fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh")
assert.NoError(t, err)
- _, allVars, err := b.executeFirstPass(fl)
+ _, allVars, err := b.scriptExecutor.ExecuteSecondPass(fl)
assert.NoError(t, err)
tc.Expected(t, allVars)
diff --git a/pkg/build/cache.go b/pkg/build/cache.go
new file mode 100644
index 0000000..6de0b7c
--- /dev/null
+++ b/pkg/build/cache.go
@@ -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 .
+
+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
+}
diff --git a/pkg/build/checker.go b/pkg/build/checker.go
new file mode 100644
index 0000000..827d274
--- /dev/null
+++ b/pkg/build/checker.go
@@ -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 .
+
+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
+}
diff --git a/pkg/build/dirs.go b/pkg/build/dirs.go
new file mode 100644
index 0000000..58038d8
--- /dev/null
+++ b/pkg/build/dirs.go
@@ -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 .
+
+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)
+}
diff --git a/pkg/build/installer.go b/pkg/build/installer.go
new file mode 100644
index 0000000..95d23fb
--- /dev/null
+++ b/pkg/build/installer.go
@@ -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 .
+
+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
+}
diff --git a/pkg/build/main_build.go b/pkg/build/main_build.go
new file mode 100644
index 0000000..038f2ef
--- /dev/null
+++ b/pkg/build/main_build.go
@@ -0,0 +1,68 @@
+// 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 .
+
+package build
+
+import (
+ "log/slog"
+ "os"
+
+ "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
+)
+
+func NewMainBuilder(
+ cfg Config,
+ repos PackageFinder,
+) *Builder {
+ slog.Info("", "uid", os.Geteuid(), "gid", os.Getegid())
+
+ 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
+}
diff --git a/pkg/build/safe.go b/pkg/build/safe.go
new file mode 100644
index 0000000..6852b60
--- /dev/null
+++ b/pkg/build/safe.go
@@ -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 .
+
+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
+}
diff --git a/pkg/build/safe_installer.go b/pkg/build/safe_installer.go
new file mode 100644
index 0000000..5bcefb4
--- /dev/null
+++ b/pkg/build/safe_installer.go
@@ -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 .
+
+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
+}
diff --git a/pkg/build/script_executor.go b/pkg/build/script_executor.go
new file mode 100644
index 0000000..1f9489d
--- /dev/null
+++ b/pkg/build/script_executor.go
@@ -0,0 +1,434 @@
+// 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 .
+
+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)), // Ограничиваем открытие файлов
+ )
+ 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
+}
diff --git a/pkg/build/script_resolver.go b/pkg/build/script_resolver.go
new file mode 100644
index 0000000..363bbba
--- /dev/null
+++ b/pkg/build/script_resolver.go
@@ -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 .
+
+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,
+ }
+}
diff --git a/pkg/build/script_view.go b/pkg/build/script_view.go
new file mode 100644
index 0000000..9347ba2
--- /dev/null
+++ b/pkg/build/script_view.go
@@ -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 .
+
+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,
+ )
+}
diff --git a/pkg/build/source_downloader.go b/pkg/build/source_downloader.go
new file mode 100644
index 0000000..715557a
--- /dev/null
+++ b/pkg/build/source_downloader.go
@@ -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 .
+
+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
+}
diff --git a/pkg/build/utils.go b/pkg/build/utils.go
index 33656c6..2d73ee0 100644
--- a/pkg/build/utils.go
+++ b/pkg/build/utils.go
@@ -39,7 +39,6 @@ import (
"github.com/goreleaser/nfpm/v2/files"
"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/types"
"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[^+]+)\+alr-(?P.+)$`)
-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{
- Name: fmt.Sprintf("%s+alr-%s", vars.Name, opts.Repository),
+ Name: fmt.Sprintf("%s+alr-%s", vars.Name, input.Repository()),
Arch: cpu.Arch(),
Version: vars.Version,
- Release: overrides.ReleasePlatformSpecific(vars.Release, info),
+ Release: overrides.ReleasePlatformSpecific(vars.Release, input.OSRelease()),
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
}
}
// Функция getPkgFormat возвращает формат пакета из менеджера пакетов,
// или ALR_PKG_FORMAT, если он установлен.
-func getPkgFormat(mgr manager.Manager) string {
+func GetPkgFormat(mgr manager.Manager) string {
pkgFormat := mgr.Format()
if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok {
pkgFormat = format
@@ -272,25 +275,9 @@ func setVersion(ctx context.Context, r *interp.Runner, to string) error {
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 возвращает имена всех предоставленных пакетов.
+/*
func packageNames(pkgs []db.Package) []string {
names := make([]string, len(pkgs))
for i, p := range pkgs {
@@ -298,6 +285,7 @@ func packageNames(pkgs []db.Package) []string {
}
return names
}
+*/
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
func removeDuplicates(slice []string) []string {
diff --git a/pkg/manager/apt_rpm.go b/pkg/manager/apt_rpm.go
index c3ce1a2..03d25e2 100644
--- a/pkg/manager/apt_rpm.go
+++ b/pkg/manager/apt_rpm.go
@@ -66,6 +66,7 @@ func (a *APTRpm) Install(opts *Opts, pkgs ...string) error {
cmd := a.getCmd(opts, "apt-get", "install")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
+ cmd.Stdout = cmd.Stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("apt-get: install: %w", err)
diff --git a/pkg/repos/pull.go b/pkg/repos/pull.go
index 835a3d1..aabfbbd 100644
--- a/pkg/repos/pull.go
+++ b/pkg/repos/pull.go
@@ -268,6 +268,7 @@ func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Ru
interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
+ interp.Dir(scriptDir),
)
}
diff --git a/repo.go b/repo.go
index b3a6ed5..d2ad6af 100644
--- a/repo.go
+++ b/repo.go
@@ -31,6 +31,7 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
+ "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
)
@@ -54,6 +55,8 @@ func AddRepoCmd() *cli.Command {
},
},
Action: func(c *cli.Context) error {
+ utils.ExitIfNotRoot()
+
ctx := c.Context
name := c.String("name")
@@ -87,6 +90,11 @@ func AddRepoCmd() *cli.Command {
os.Exit(1)
}
+ if utils.DropCapsToAlrUser() != nil {
+ slog.Error(gotext.Get("Can't drop privileges"))
+ os.Exit(1)
+ }
+
db := database.New(cfg)
err = db.Init(ctx)
if err != nil {
@@ -119,6 +127,8 @@ func RemoveRepoCmd() *cli.Command {
},
},
Action: func(c *cli.Context) error {
+ utils.ExitIfNotRoot()
+
ctx := c.Context
name := c.String("name")
@@ -179,6 +189,11 @@ func RefreshCmd() *cli.Command {
Usage: gotext.Get("Pull all repositories that have changed"),
Aliases: []string{"ref"},
Action: func(c *cli.Context) error {
+ if utils.DropCapsToAlrUser() != nil {
+ slog.Error(gotext.Get("Can't drop privileges"))
+ os.Exit(1)
+ }
+
ctx := c.Context
cfg := config.New()
err := cfg.Load()
diff --git a/search.go b/search.go
index e233ca9..43fb24a 100644
--- a/search.go
+++ b/search.go
@@ -27,6 +27,7 @@ import (
"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/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search"
)
@@ -63,6 +64,11 @@ func SearchCmd() *cli.Command {
},
},
Action: func(c *cli.Context) error {
+ if utils.DropCapsToAlrUser() != nil {
+ slog.Error(gotext.Get("Can't drop privileges"))
+ os.Exit(1)
+ }
+
ctx := c.Context
cfg := config.New()
err := cfg.Load()
diff --git a/upgrade.go b/upgrade.g_o
similarity index 100%
rename from upgrade.go
rename to upgrade.g_o