From 8070112bf200a7f25a753bf1a09490bfb84f0e69 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Sat, 12 Apr 2025 15:51:39 +0300 Subject: [PATCH] wip --- Makefile | 1 + assets/coverage-badge.svg | 4 +- assets/i18n-ru-badge.svg | 4 +- build.go | 53 +- e2e-tests/addrepo_test.go | 6 +- e2e-tests/common_test.go | 6 +- e2e-tests/images/Dockerfile.fedora-41 | 19 +- e2e-tests/images/Dockerfile.ubuntu-24.04 | 20 +- e2e-tests/issue_41_autoreq_skiplist_test.go | 1 + e2e-tests/issue_50_install_multiple_test.go | 1 + e2e-tests/issue_53_lc_all_c_info_test.go | 1 + e2e-tests/issue_59_rm_completion_test.go | 1 + fix.go | 26 +- go.mod | 17 +- go.sum | 58 +- install.go | 51 +- internal.go | 133 ++ internal/config/config.go | 50 +- internal/logger/hclog.go | 136 ++ internal/logger/log.go | 80 +- internal/shutils/handlers/fakeroot.go | 50 +- internal/translations/default.pot | 208 ++- internal/translations/po/ru/default.po | 258 ++-- internal/types/build.go | 10 +- internal/utils/cmd.go | 68 + main.go | 63 +- pkg/build/build.go | 1185 +++++++---------- ...ternal_test.go => build_internal_test.g_o} | 2 +- pkg/build/cache.go | 69 + pkg/build/checker.go | 74 + pkg/build/dirs.go | 71 + pkg/build/installer.go | 61 + pkg/build/main_build.go | 68 + pkg/build/safe.go | 263 ++++ pkg/build/safe_installer.go | 132 ++ pkg/build/script_executor.go | 434 ++++++ pkg/build/script_resolver.go | 53 + pkg/build/script_view.go | 46 + pkg/build/source_downloader.go | 86 ++ pkg/build/utils.go | 32 +- pkg/manager/apt_rpm.go | 1 + pkg/repos/pull.go | 1 + repo.go | 15 + search.go | 6 + upgrade.go => upgrade.g_o | 0 45 files changed, 2728 insertions(+), 1196 deletions(-) create mode 100644 internal.go create mode 100644 internal/logger/hclog.go create mode 100644 internal/utils/cmd.go rename pkg/build/{build_internal_test.go => build_internal_test.g_o} (99%) create mode 100644 pkg/build/cache.go create mode 100644 pkg/build/checker.go create mode 100644 pkg/build/dirs.go create mode 100644 pkg/build/installer.go create mode 100644 pkg/build/main_build.go create mode 100644 pkg/build/safe.go create mode 100644 pkg/build/safe_installer.go create mode 100644 pkg/build/script_executor.go create mode 100644 pkg/build/script_resolver.go create mode 100644 pkg/build/script_view.go create mode 100644 pkg/build/source_downloader.go rename upgrade.go => upgrade.g_o (100%) 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