Compare commits
	
		
			35 Commits
		
	
	
		
			7a3acfe5c1
			...
			v0.0.11
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2b7c2bbbb3 | |||
| afe35f407e | |||
| c51d1d9202 | |||
| b46dd41ada | |||
| f623cba5c0 | |||
| e552663442 | |||
| 7bbceb76c9 | |||
| bd6e3bbe27 | |||
| 0d917190ab | |||
| 83b8f3b047 | |||
| 3483cf57f8 | |||
| efa4f59403 | |||
| c59ed6d505 | |||
| 2bbc308810 | |||
| 5ca34a572a | |||
| 67e63d1831 | |||
| 0bfe88beed | |||
| 3ce9f0db35 | |||
| 469a05105a | |||
| 70aca61750 | |||
| 4b35f5e4e6 | |||
| 7770675a8d | |||
| bd79d56776 | |||
| fff8b777fe | |||
| 6bee268ea9 | |||
| 4b53e819d8 | |||
| 6c0e8aeb3f | |||
| cbc6b9f452 | |||
| c705c25613 | |||
| 8f4b021a93 | |||
| 5e7d4033e4 | |||
| 4ac2432770 | |||
| 3c37310f0d | |||
| d300ab197b | |||
| eb2cc3c1e6 | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -8,3 +8,6 @@ | ||||
| .gigaide | ||||
|  | ||||
| *.out | ||||
|  | ||||
| e2e-tests/alr | ||||
| commit_msg.txt | ||||
							
								
								
									
										9
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								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 $< $@ | ||||
| @@ -71,3 +72,11 @@ i18n: | ||||
| test-coverage: | ||||
| 	go test ./... -v -coverpkg=./... -coverprofile=coverage.out | ||||
| 	bash scripts/coverage-badge.sh | ||||
|  | ||||
| update-deps-cve: | ||||
| 	bash scripts/update-deps-cve.sh | ||||
|  | ||||
| e2e-test: clean build | ||||
| 	rm -f ./e2e-tests/alr | ||||
| 	cp alr e2e-tests | ||||
| 	go test -tags=e2e ./... | ||||
| @@ -11,7 +11,7 @@ | ||||
|     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||
|         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> | ||||
|         <text x="33.5" y="14">coverage</text> | ||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">19.8%</text> | ||||
|         <text x="86" y="14">19.8%</text> | ||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">16.4%</text> | ||||
|         <text x="86" y="14">16.4%</text> | ||||
|     </g> | ||||
| </svg> | ||||
|   | ||||
| Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B | 
							
								
								
									
										182
									
								
								build.go
									
									
									
									
									
								
							
							
						
						
									
										182
									
								
								build.go
									
									
									
									
									
								
							| @@ -28,14 +28,12 @@ import ( | ||||
| 	"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/cliutils" | ||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/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" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||
| ) | ||||
|  | ||||
| func BuildCmd() *cli.Command { | ||||
| @@ -66,26 +64,67 @@ func BuildCmd() *cli.Command { | ||||
| 			}, | ||||
| 		}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			rs := repos.New(cfg, db) | ||||
| 			err := db.Init(ctx) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			if err := utils.EnuseIsPrivilegedGroupMember(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			wd, err := os.Getwd() | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) | ||||
| 			} | ||||
|  | ||||
| 			wd, wdCleanup, err := Mount(wd) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer wdCleanup() | ||||
|  | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithReposNoPull(). | ||||
| 				WithDistroInfo(). | ||||
| 				WithManager(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return cli.Exit(err, 1) | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			var script string | ||||
| 			var packages []string | ||||
| 			repository := "default" | ||||
|  | ||||
| 			repoDir := cfg.GetPaths(ctx).RepoDir | ||||
| 			var res *build.BuildResult | ||||
|  | ||||
| 			var scriptArgs *build.BuildPackageFromScriptArgs | ||||
| 			var dbArgs *build.BuildPackageFromDbArgs | ||||
|  | ||||
| 			buildArgs := &build.BuildArgs{ | ||||
| 				Opts: &types.BuildOpts{ | ||||
| 					Clean:       c.Bool("clean"), | ||||
| 					Interactive: c.Bool("interactive"), | ||||
| 				}, | ||||
| 				PkgFormat_: build.GetPkgFormat(deps.Manager), | ||||
| 				Info:       deps.Info, | ||||
| 			} | ||||
|  | ||||
| 			switch { | ||||
| 			case c.IsSet("script"): | ||||
| 				script = c.String("script") | ||||
| 				script, err = filepath.Abs(c.String("script")) | ||||
| 				if err != nil { | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Cannot get absolute script path"), err) | ||||
| 				} | ||||
|  | ||||
| 				packages = append(packages, c.String("script-package")) | ||||
|  | ||||
| 				scriptArgs = &build.BuildPackageFromScriptArgs{ | ||||
| 					Script:    script, | ||||
| 					Packages:  packages, | ||||
| 					BuildArgs: *buildArgs, | ||||
| 				} | ||||
| 			case c.IsSet("package"): | ||||
| 				// TODO: handle multiple packages | ||||
| 				packageInput := c.String("package") | ||||
| @@ -98,86 +137,97 @@ func BuildCmd() *cli.Command { | ||||
| 					packageSearch = arr[0] | ||||
| 				} | ||||
|  | ||||
| 				pkgs, _, _ := rs.FindPkgs(ctx, []string{packageSearch}) | ||||
| 				pkg, ok := pkgs[packageSearch] | ||||
| 				if len(pkg) < 1 || !ok { | ||||
| 					slog.Error(gotext.Get("Package not found")) | ||||
| 					os.Exit(1) | ||||
| 				pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageSearch}) | ||||
| 				if err != nil { | ||||
| 					return cliutils.FormatCliExit("failed to find pkgs", err) | ||||
| 				} | ||||
|  | ||||
| 				repository = pkg[0].Repository | ||||
| 				pkg := cliutils.FlattenPkgs(ctx, pkgs, "build", c.Bool("interactive")) | ||||
|  | ||||
| 				if len(pkg) < 1 { | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Package not found"), nil) | ||||
| 				} | ||||
|  | ||||
| 				if pkg[0].BasePkgName != "" { | ||||
| 					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") | ||||
| 				} | ||||
|  | ||||
| 				dbArgs = &build.BuildPackageFromDbArgs{ | ||||
| 					Package:   &pkg[0], | ||||
| 					Packages:  packages, | ||||
| 					BuildArgs: *buildArgs, | ||||
| 				} | ||||
| 			default: | ||||
| 				script = filepath.Join(repoDir, "alr.sh") | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil) | ||||
| 			} | ||||
|  | ||||
| 			// Проверка автоматического пулла репозиториев | ||||
| 			if cfg.AutoPull(ctx) { | ||||
| 				err := rs.Pull(ctx, cfg.Repos(ctx)) | ||||
| 			if scriptArgs != nil { | ||||
| 				scriptFile := filepath.Base(scriptArgs.Script) | ||||
| 				newScriptDir, scriptDirCleanup, err := Mount(filepath.Dir(scriptArgs.Script)) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error pulling repositories"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 					return err | ||||
| 				} | ||||
| 				defer scriptDirCleanup() | ||||
| 				scriptArgs.Script = filepath.Join(newScriptDir, scriptFile) | ||||
| 			} | ||||
|  | ||||
| 			// Обнаружение менеджера пакетов | ||||
| 			mgr := manager.Detect() | ||||
| 			if mgr == nil { | ||||
| 				slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) | ||||
| 				os.Exit(1) | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			info, err := distro.ParseOSRelease(ctx) | ||||
| 			installer, installerClose, err := build.GetSafeInstaller() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error parsing os release"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
| 			defer installerClose() | ||||
|  | ||||
| 			if err := utils.ExitIfCantSetNoNewPrivs(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			builder := build.NewBuilder( | ||||
| 				ctx, | ||||
| 				types.BuildOpts{ | ||||
| 					Packages:    packages, | ||||
| 					Repository:  repository, | ||||
| 					Script:      script, | ||||
| 					Manager:     mgr, | ||||
| 					Clean:       c.Bool("clean"), | ||||
| 					Interactive: c.Bool("interactive"), | ||||
| 				}, | ||||
| 				rs, | ||||
| 				info, | ||||
| 				cfg, | ||||
| 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer scripterClose() | ||||
|  | ||||
| 			builder, err := build.NewMainBuilder( | ||||
| 				deps.Cfg, | ||||
| 				deps.Manager, | ||||
| 				deps.Repos, | ||||
| 				scripter, | ||||
| 				installer, | ||||
| 			) | ||||
|  | ||||
| 			// Сборка пакета | ||||
| 			pkgPaths, _, err := builder.BuildPackage(ctx) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error building package"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			// Получение текущей рабочей директории | ||||
| 			wd, err := os.Getwd() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error getting working directory"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			if scriptArgs != nil { | ||||
| 				res, err = builder.BuildPackageFromScript( | ||||
| 					ctx, | ||||
| 					scriptArgs, | ||||
| 				) | ||||
| 			} else if dbArgs != nil { | ||||
| 				res, err = builder.BuildPackageFromDb( | ||||
| 					ctx, | ||||
| 					dbArgs, | ||||
| 				) | ||||
| 			} | ||||
|  | ||||
| 			// Перемещение собранных пакетов в рабочую директорию | ||||
| 			for _, pkgPath := range pkgPaths { | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error building package"), err) | ||||
| 			} | ||||
|  | ||||
| 			for _, pkgPath := range res.PackagePaths { | ||||
| 				name := filepath.Base(pkgPath) | ||||
| 				err = osutils.Move(pkgPath, filepath.Join(wd, name)) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error moving the package"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			slog.Info(gotext.Get("Done")) | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										72
									
								
								e2e-tests/addrepo_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								e2e-tests/addrepo_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/efficientgo/e2e" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestE2EAlrAddRepo(t *testing.T) { | ||||
| 	dockerMultipleRun( | ||||
| 		t, | ||||
| 		"add-repo-remove-repo", | ||||
| 		COMMON_SYSTEMS, | ||||
| 		func(t *testing.T, r e2e.Runnable) { | ||||
| 			err := r.Exec(e2e.NewCommand( | ||||
| 				"sudo", | ||||
| 				"alr", | ||||
| 				"addrepo", | ||||
| 				"--name", | ||||
| 				"alr-repo", | ||||
| 				"--url", | ||||
| 				"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"bash", | ||||
| 				"-c", | ||||
| 				"cat /etc/alr/alr.toml", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"sudo", | ||||
| 				"alr", | ||||
| 				"removerepo", | ||||
| 				"--name", | ||||
| 				"alr-repo", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			var buf bytes.Buffer | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"bash", | ||||
| 				"-c", | ||||
| 				"cat /etc/alr/alr.toml", | ||||
| 			), e2e.WithExecOptionStdout(&buf)) | ||||
| 			assert.NoError(t, err) | ||||
| 			assert.Contains(t, buf.String(), "rootCmd") | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										40
									
								
								e2e-tests/bash_completion_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								e2e-tests/bash_completion_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/alecthomas/assert/v2" | ||||
| 	"github.com/efficientgo/e2e" | ||||
| ) | ||||
|  | ||||
| func TestE2EBashCompletion(t *testing.T) { | ||||
| 	dockerMultipleRun( | ||||
| 		t, | ||||
| 		"bash-completion", | ||||
| 		COMMON_SYSTEMS, | ||||
| 		func(t *testing.T, r e2e.Runnable) { | ||||
| 			err := r.Exec(e2e.NewCommand( | ||||
| 				"alr", "install", "--generate-bash-completion", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										190
									
								
								e2e-tests/common_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								e2e-tests/common_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"crypto/sha256" | ||||
| 	"encoding/hex" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/efficientgo/e2e" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	expect "github.com/tailscale/goexpect" | ||||
| ) | ||||
|  | ||||
| // DebugWriter оборачивает io.Writer и логирует все записываемые данные. | ||||
| type DebugWriter struct { | ||||
| 	prefix string | ||||
| 	writer io.Writer | ||||
| } | ||||
|  | ||||
| func (d *DebugWriter) Write(p []byte) (n int, err error) { | ||||
| 	log.Printf("%s: Writing data: %q", d.prefix, p) // Логируем данные | ||||
| 	return d.writer.Write(p) | ||||
| } | ||||
|  | ||||
| // DebugReader оборачивает io.Reader и логирует все читаемые данные. | ||||
| type DebugReader struct { | ||||
| 	prefix string | ||||
| 	reader io.Reader | ||||
| } | ||||
|  | ||||
| func (d *DebugReader) Read(p []byte) (n int, err error) { | ||||
| 	n, err = d.reader.Read(p) | ||||
| 	if n > 0 { | ||||
| 		log.Printf("%s: Read data: %q", d.prefix, p[:n]) // Логируем данные | ||||
| 	} | ||||
| 	return n, err | ||||
| } | ||||
|  | ||||
| func e2eSpawn(runnable e2e.Runnable, command e2e.Command, timeout time.Duration, opts ...expect.Option) (expect.Expecter, <-chan error, error, *io.PipeWriter) { | ||||
| 	resCh := make(chan error) | ||||
|  | ||||
| 	// Создаем pipe для stdin и stdout | ||||
| 	stdinReader, stdinWriter := io.Pipe() | ||||
| 	stdoutReader, stdoutWriter := io.Pipe() | ||||
|  | ||||
| 	debugStdinReader := &DebugReader{prefix: "STDIN", reader: stdinReader} | ||||
| 	debugStdoutWriter := &DebugWriter{prefix: "STDOUT", writer: stdoutWriter} | ||||
|  | ||||
| 	go func() { | ||||
| 		err := runnable.Exec( | ||||
| 			command, | ||||
| 			e2e.WithExecOptionStdout(debugStdoutWriter), | ||||
| 			e2e.WithExecOptionStdin(debugStdinReader), | ||||
| 			e2e.WithExecOptionStderr(debugStdoutWriter), | ||||
| 		) | ||||
|  | ||||
| 		resCh <- err | ||||
| 	}() | ||||
|  | ||||
| 	exp, chnErr, err := expect.SpawnGeneric(&expect.GenOptions{ | ||||
| 		In:  stdinWriter, | ||||
| 		Out: stdoutReader, | ||||
| 		Wait: func() error { | ||||
| 			return <-resCh | ||||
| 		}, | ||||
| 		Close: func() error { | ||||
| 			stdinWriter.Close() | ||||
| 			stdoutReader.Close() | ||||
| 			return nil | ||||
| 		}, | ||||
| 		Check: func() bool { return true }, | ||||
| 	}, timeout, expect.Verbose(true), expect.VerboseWriter(os.Stdout)) | ||||
|  | ||||
| 	return exp, chnErr, err, stdinWriter | ||||
| } | ||||
|  | ||||
| var ALL_SYSTEMS []string = []string{ | ||||
| 	"ubuntu-24.04", | ||||
| 	"alt-sisyphus", | ||||
| 	"fedora-41", | ||||
| 	// "archlinux", | ||||
| 	// "alpine", | ||||
| 	// "opensuse-leap", | ||||
| 	// "redos-8", | ||||
| } | ||||
|  | ||||
| var AUTOREQ_AUTOPROV_SYSTEMS []string = []string{ | ||||
| 	// "alt-sisyphus", | ||||
| 	"fedora-41", | ||||
| } | ||||
|  | ||||
| var RPM_SYSTEMS []string = []string{ | ||||
| 	"fedora-41", | ||||
| } | ||||
|  | ||||
| var COMMON_SYSTEMS []string = []string{ | ||||
| 	"ubuntu-24.04", | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	for _, id := range ALL_SYSTEMS { | ||||
| 		buildAlrTestImage(id) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func buildAlrTestImage(id string) { | ||||
| 	cmd := exec.Command( | ||||
| 		"docker", | ||||
| 		"build", | ||||
| 		"-t", fmt.Sprintf("alr-testimage-%s", id), | ||||
| 		"-f", fmt.Sprintf("images/Dockerfile.%s", id), | ||||
| 		".", | ||||
| 	) | ||||
| 	cmd.Stdout = os.Stdout | ||||
| 	cmd.Stderr = os.Stderr | ||||
| 	err := cmd.Run() | ||||
| 	if err != nil { | ||||
| 		fmt.Println("Error:", err) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testing.T, runnable e2e.Runnable)) { | ||||
| 	t.Run(name, func(t *testing.T) { | ||||
| 		for _, id := range ids { | ||||
| 			t.Run(id, func(t *testing.T) { | ||||
| 				t.Parallel() | ||||
| 				dockerName := fmt.Sprintf("alr-test-%s-%s", name, id) | ||||
| 				hash := sha256.New() | ||||
| 				hash.Write([]byte(dockerName)) | ||||
| 				hashSum := hash.Sum(nil) | ||||
| 				hashString := hex.EncodeToString(hashSum) | ||||
| 				truncatedHash := hashString[:8] | ||||
| 				e, err := e2e.New(e2e.WithVerbose(), e2e.WithName(fmt.Sprintf("alr-%s", truncatedHash))) | ||||
| 				assert.NoError(t, err) | ||||
| 				t.Cleanup(e.Close) | ||||
| 				imageId := fmt.Sprintf("alr-testimage-%s", id) | ||||
| 				runnable := e.Runnable(dockerName).Init( | ||||
| 					e2e.StartOptions{ | ||||
| 						Image:   imageId, | ||||
| 						Volumes: []string{ | ||||
| 							// "./alr:/usr/bin/alr", | ||||
| 						}, | ||||
| 						Privileged: true, | ||||
| 					}, | ||||
| 				) | ||||
| 				assert.NoError(t, e2e.StartAndWaitReady(runnable)) | ||||
| 				f(t, runnable) | ||||
| 			}) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expects []expect.Batcher) { | ||||
| 	exp, _, err, _ := e2eSpawn( | ||||
| 		r, | ||||
| 		e2e.NewCommand("/bin/bash"), 25*time.Second, | ||||
| 		expect.Verbose(true), | ||||
| 	) | ||||
| 	assert.NoError(t, err) | ||||
| 	_, err = exp.ExpectBatch( | ||||
| 		expects, | ||||
| 		timeout, | ||||
| 	) | ||||
| 	assert.NoError(t, err) | ||||
| } | ||||
							
								
								
									
										43
									
								
								e2e-tests/fix_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								e2e-tests/fix_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/efficientgo/e2e" | ||||
| 	expect "github.com/tailscale/goexpect" | ||||
| ) | ||||
|  | ||||
| func TestE2EAlrFix(t *testing.T) { | ||||
| 	dockerMultipleRun( | ||||
| 		t, | ||||
| 		"run-fix", | ||||
| 		COMMON_SYSTEMS, | ||||
| 		func(t *testing.T, r e2e.Runnable) { | ||||
| 			runTestCommands(t, r, time.Second*30, []expect.Batcher{ | ||||
| 				&expect.BSnd{S: "alr fix\n"}, | ||||
| 				&expect.BExp{R: `--> Done`}, | ||||
| 				&expect.BSnd{S: "echo $?\n"}, | ||||
| 				&expect.BExp{R: `^0\n$`}, | ||||
| 			}) | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										56
									
								
								e2e-tests/group_and_summary_field_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								e2e-tests/group_and_summary_field_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/alecthomas/assert/v2" | ||||
| 	"github.com/efficientgo/e2e" | ||||
| ) | ||||
|  | ||||
| func TestE2EGroupAndSummaryField(t *testing.T) { | ||||
| 	dockerMultipleRun( | ||||
| 		t, | ||||
| 		"group-and-summary-field", | ||||
| 		RPM_SYSTEMS, | ||||
| 		func(t *testing.T, r e2e.Runnable) { | ||||
| 			err := r.Exec(e2e.NewCommand( | ||||
| 				"sudo", | ||||
| 				"alr", | ||||
| 				"addrepo", | ||||
| 				"--name", | ||||
| 				"alr-repo", | ||||
| 				"--url", | ||||
| 				"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group}}\" | grep ^System/Base$", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary}}\" | grep \"^Custom summary$\"", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										4
									
								
								e2e-tests/images/Dockerfile.alpine
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								e2e-tests/images/Dockerfile.alpine
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| FROM alpine:latest | ||||
| RUN adduser -s /bin/bash alr-user | ||||
| USER alr-user | ||||
| ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||
							
								
								
									
										6
									
								
								e2e-tests/images/Dockerfile.alt-sisyphus
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								e2e-tests/images/Dockerfile.alt-sisyphus
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| FROM registry.altlinux.org/sisyphus/alt:latest | ||||
| RUN apt-get update && apt-get install -y ca-certificates rpm-build | ||||
| RUN useradd -m -s /bin/bash alr-user | ||||
| USER alr-user | ||||
| WORKDIR /home/alr-user | ||||
| ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||
							
								
								
									
										4
									
								
								e2e-tests/images/Dockerfile.archlinux
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								e2e-tests/images/Dockerfile.archlinux
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| FROM archlinux:latest | ||||
| RUN useradd -m -s /bin/bash alr-user | ||||
| USER alr-user | ||||
| ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||
							
								
								
									
										18
									
								
								e2e-tests/images/Dockerfile.fedora-41
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								e2e-tests/images/Dockerfile.fedora-41
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| FROM fedora:41 | ||||
| RUN dnf install -y ca-certificates sudo rpm-build bindfs | ||||
| RUN <<EOF | ||||
|     useradd -m -s /bin/bash -G wheel user | ||||
|     echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/user | ||||
|     chmod 0440 /etc/sudoers.d/user | ||||
|  | ||||
|     useradd -m -s /bin/bash alr | ||||
|     mkdir -p /var/cache/alr /etc/alr | ||||
|     chown alr:alr /var/cache/alr /etc/alr | ||||
| EOF | ||||
| COPY ./alr /usr/bin | ||||
| RUN <<EOF | ||||
|     setcap cap_setuid,cap_setgid+ep /usr/bin/alr | ||||
| EOF | ||||
| USER user | ||||
| WORKDIR /home/user | ||||
| ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||
							
								
								
									
										4
									
								
								e2e-tests/images/Dockerfile.opensuse-leap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								e2e-tests/images/Dockerfile.opensuse-leap
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| FROM opensuse/leap:latest | ||||
| RUN useradd -m -s /bin/bash alr-user | ||||
| USER alr-user | ||||
| ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||
							
								
								
									
										4
									
								
								e2e-tests/images/Dockerfile.redos-8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								e2e-tests/images/Dockerfile.redos-8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| FROM registry.red-soft.ru/ubi8/ubi:latest | ||||
| RUN useradd -m -s /bin/bash alr-user | ||||
| USER alr-user | ||||
| ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||
							
								
								
									
										17
									
								
								e2e-tests/images/Dockerfile.ubuntu-24.04
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								e2e-tests/images/Dockerfile.ubuntu-24.04
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| FROM ubuntu:24.10 | ||||
| RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates sudo libcap2-bin | ||||
| RUN <<EOF | ||||
|     useradd -m -s /bin/bash user | ||||
|     echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/user | ||||
|     chmod 0440 /etc/sudoers.d/user  | ||||
|      | ||||
|     useradd -m -s /bin/bash alr | ||||
|     mkdir -p /var/cache/alr /etc/alr | ||||
|     chown alr:alr /var/cache/alr /etc/alr | ||||
| EOF | ||||
| COPY ./alr /usr/bin | ||||
| RUN <<EOF | ||||
|     setcap cap_setuid,cap_setgid+ep /usr/bin/alr | ||||
| EOF | ||||
| USER user | ||||
| ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||
							
								
								
									
										51
									
								
								e2e-tests/issue_32_interactive_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								e2e-tests/issue_32_interactive_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/alecthomas/assert/v2" | ||||
| 	"github.com/efficientgo/e2e" | ||||
| ) | ||||
|  | ||||
| func TestE2EIssue32Interactive(t *testing.T) { | ||||
| 	dockerMultipleRun( | ||||
| 		t, | ||||
| 		"issue-32-interactive", | ||||
| 		COMMON_SYSTEMS, | ||||
| 		func(t *testing.T, r e2e.Runnable) { | ||||
| 			assert.NoError(t, r.Exec(e2e.NewCommand( | ||||
| 				"sudo", "alr", "--interactive=false", "remove", "ca-certificates", | ||||
| 			))) | ||||
|  | ||||
| 			assert.NoError(t, r.Exec(e2e.NewCommand( | ||||
| 				"sudo", "alr", "--interactive=false", "remove", "openssl", | ||||
| 			))) | ||||
|  | ||||
| 			assert.NoError(t, r.Exec(e2e.NewCommand( | ||||
| 				"alr", "fix", | ||||
| 			))) | ||||
|  | ||||
| 			assert.NoError(t, r.Exec(e2e.NewCommand( | ||||
| 				"sudo", "alr", "--interactive=false", "install", "ca-certificates", | ||||
| 			))) | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										81
									
								
								e2e-tests/issue_41_autoreq_skiplist_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								e2e-tests/issue_41_autoreq_skiplist_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/alecthomas/assert/v2" | ||||
| 	"github.com/efficientgo/e2e" | ||||
| ) | ||||
|  | ||||
| func TestE2EIssue41AutoreqSkiplist(t *testing.T) { | ||||
| 	dockerMultipleRun( | ||||
| 		t, | ||||
| 		"issue-41-autoreq-skiplist", | ||||
| 		AUTOREQ_AUTOPROV_SYSTEMS, | ||||
| 		func(t *testing.T, r e2e.Runnable) { | ||||
| 			err := r.Exec(e2e.NewCommand( | ||||
| 				"sudo", | ||||
| 				"alr", | ||||
| 				"addrepo", | ||||
| 				"--name", | ||||
| 				"alr-repo", | ||||
| 				"--url", | ||||
| 				"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"alr", | ||||
| 				"ref", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"alr", | ||||
| 				"build", | ||||
| 				"-p", | ||||
| 				"alr-repo/test-autoreq-autoprov", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"sh", | ||||
| 				"-c", | ||||
| 				"rpm -qp --requires *.rpm | grep \"^/bin/sh$\"", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"sh", | ||||
| 				"-c", | ||||
| 				"rpm -qp --requires *.rpm | grep \"^/bin/bash$\"", | ||||
| 			)) | ||||
| 			assert.Error(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"sh", | ||||
| 				"-c", | ||||
| 				"rpm -qp --requires *.rpm | grep \"^/bin/zsh$\"", | ||||
| 			)) | ||||
| 			assert.Error(t, err) | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										56
									
								
								e2e-tests/issue_50_install_multiple_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								e2e-tests/issue_50_install_multiple_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/alecthomas/assert/v2" | ||||
| 	"github.com/efficientgo/e2e" | ||||
| ) | ||||
|  | ||||
| func TestE2EIssue50InstallMultiple(t *testing.T) { | ||||
| 	dockerMultipleRun( | ||||
| 		t, | ||||
| 		"issue-50-install-multiple", | ||||
| 		COMMON_SYSTEMS, | ||||
| 		func(t *testing.T, r e2e.Runnable) { | ||||
| 			err := r.Exec(e2e.NewCommand( | ||||
| 				"sudo", | ||||
| 				"alr", | ||||
| 				"addrepo", | ||||
| 				"--name", | ||||
| 				"alr-repo", | ||||
| 				"--url", | ||||
| 				"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"sudo", "alr", "in", "foo-pkg", "bar-pkg", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand("cat", "/opt/foo")) | ||||
| 			assert.NoError(t, err) | ||||
| 			err = r.Exec(e2e.NewCommand("cat", "/opt/bar")) | ||||
| 			assert.NoError(t, err) | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										53
									
								
								e2e-tests/issue_53_lc_all_c_info_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								e2e-tests/issue_53_lc_all_c_info_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/alecthomas/assert/v2" | ||||
| 	"github.com/efficientgo/e2e" | ||||
| ) | ||||
|  | ||||
| func TestE2EIssue53LcAllCInfo(t *testing.T) { | ||||
| 	dockerMultipleRun( | ||||
| 		t, | ||||
| 		"issue-53-lc-all-c-info", | ||||
| 		COMMON_SYSTEMS, | ||||
| 		func(t *testing.T, r e2e.Runnable) { | ||||
| 			err := r.Exec(e2e.NewCommand( | ||||
| 				"sudo", | ||||
| 				"alr", | ||||
| 				"addrepo", | ||||
| 				"--name", | ||||
| 				"alr-repo", | ||||
| 				"--url", | ||||
| 				"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"bash", | ||||
| 				"-c", | ||||
| 				"LANG=C alr info alr-bin", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										58
									
								
								e2e-tests/issue_59_rm_completion_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								e2e-tests/issue_59_rm_completion_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/alecthomas/assert/v2" | ||||
| 	"github.com/efficientgo/e2e" | ||||
| ) | ||||
|  | ||||
| func TestE2EIssue59RmCompletion(t *testing.T) { | ||||
| 	dockerMultipleRun( | ||||
| 		t, | ||||
| 		"issue-59-rm-completion", | ||||
| 		COMMON_SYSTEMS, | ||||
| 		func(t *testing.T, r e2e.Runnable) { | ||||
| 			err := r.Exec(e2e.NewCommand( | ||||
| 				"sudo", | ||||
| 				"alr", | ||||
| 				"addrepo", | ||||
| 				"--name", | ||||
| 				"alr-repo", | ||||
| 				"--url", | ||||
| 				"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand( | ||||
| 				"sudo", "alr", "in", "foo-pkg", "bar-pkg", | ||||
| 			)) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^foo-pkg$")) | ||||
| 			assert.NoError(t, err) | ||||
| 			err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^bar-pkg$")) | ||||
| 			assert.NoError(t, err) | ||||
| 			err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^test-autoreq-autoprov$")) | ||||
| 			assert.Error(t, err) | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										44
									
								
								e2e-tests/version_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								e2e-tests/version_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/efficientgo/e2e" | ||||
|  | ||||
| 	expect "github.com/tailscale/goexpect" | ||||
| ) | ||||
|  | ||||
| func TestE2EAlrVersion(t *testing.T) { | ||||
| 	dockerMultipleRun( | ||||
| 		t, | ||||
| 		"check-version", | ||||
| 		COMMON_SYSTEMS, | ||||
| 		func(t *testing.T, r e2e.Runnable) { | ||||
| 			runTestCommands(t, r, time.Second*10, []expect.Batcher{ | ||||
| 				&expect.BSnd{S: "alr version\n"}, | ||||
| 				&expect.BExp{R: `^v\d+\.\d+\.\d+(?:-\d+-g[a-f0-9]+)?\n$`}, | ||||
| 				&expect.BSnd{S: "echo $?\n"}, | ||||
| 				&expect.BExp{R: `^0\n$`}, | ||||
| 			}) | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										71
									
								
								fix.go
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								fix.go
									
									
									
									
									
								
							| @@ -22,13 +22,14 @@ package main | ||||
| import ( | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"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/pkg/repos" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||
| ) | ||||
|  | ||||
| func FixCmd() *cli.Command { | ||||
| @@ -36,39 +37,63 @@ func FixCmd() *cli.Command { | ||||
| 		Name:  "fix", | ||||
| 		Usage: gotext.Get("Attempt to fix problems with ALR"), | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
| 			paths := cfg.GetPaths(ctx) | ||||
|  | ||||
| 			slog.Info(gotext.Get("Removing cache directory")) | ||||
|  | ||||
| 			err := os.RemoveAll(paths.CacheDir) | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Unable to remove cache directory"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cli.Exit(err, 1) | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			cfg := deps.Cfg | ||||
|  | ||||
| 			paths := cfg.GetPaths() | ||||
|  | ||||
| 			slog.Info(gotext.Get("Clearing cache directory")) | ||||
| 			// Remove all nested directories of paths.CacheDir | ||||
|  | ||||
| 			dir, err := os.Open(paths.CacheDir) | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err) | ||||
| 			} | ||||
| 			defer dir.Close() | ||||
|  | ||||
| 			entries, err := dir.Readdirnames(-1) | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err) | ||||
| 			} | ||||
|  | ||||
| 			for _, entry := range entries { | ||||
| 				err = os.RemoveAll(filepath.Join(paths.CacheDir, entry)) | ||||
| 				if err != nil { | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			slog.Info(gotext.Get("Rebuilding cache")) | ||||
|  | ||||
| 			err = os.MkdirAll(paths.CacheDir, 0o755) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Unable to create new cache directory"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err) | ||||
| 			} | ||||
|  | ||||
| 			cfg = config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			err = db.Init(ctx) | ||||
| 			deps, err = appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithReposForcePull(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			rs := repos.New(cfg, db) | ||||
| 			err = rs.Pull(ctx, cfg.Repos(ctx)) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cli.Exit(err, 1) | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			slog.Info(gotext.Get("Done")) | ||||
|  | ||||
|   | ||||
							
								
								
									
										52
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								go.mod
									
									
									
									
									
								
							| @@ -1,22 +1,27 @@ | ||||
| module gitea.plemya-x.ru/Plemya-x/ALR | ||||
|  | ||||
| go 1.22 | ||||
| go 1.23.0 | ||||
|  | ||||
| toolchain go1.23.5 | ||||
| toolchain go1.24.2 | ||||
|  | ||||
| require ( | ||||
| 	gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1 | ||||
| 	gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 | ||||
| 	github.com/AlecAivazis/survey/v2 v2.3.7 | ||||
| 	github.com/PuerkitoBio/purell v1.2.0 | ||||
| 	github.com/alecthomas/assert/v2 v2.2.1 | ||||
| 	github.com/alecthomas/chroma/v2 v2.9.1 | ||||
| 	github.com/caarlos0/env v3.5.0+incompatible | ||||
| 	github.com/charmbracelet/bubbles v0.20.0 | ||||
| 	github.com/charmbracelet/bubbletea v1.2.4 | ||||
| 	github.com/charmbracelet/lipgloss v1.0.0 | ||||
| 	github.com/charmbracelet/log v0.4.0 | ||||
| 	github.com/go-git/go-billy/v5 v5.5.0 | ||||
| 	github.com/go-git/go-git/v5 v5.12.0 | ||||
| 	github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 | ||||
| 	github.com/go-git/go-billy/v5 v5.6.0 | ||||
| 	github.com/go-git/go-git/v5 v5.13.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 | ||||
| @@ -26,13 +31,14 @@ require ( | ||||
| 	github.com/muesli/reflow v0.3.0 | ||||
| 	github.com/pelletier/go-toml/v2 v2.1.0 | ||||
| 	github.com/stretchr/testify v1.10.0 | ||||
| 	github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 | ||||
| 	github.com/urfave/cli/v2 v2.25.7 | ||||
| 	github.com/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/exp v0.0.0-20231206192017-f3f8817b8deb | ||||
| 	golang.org/x/sys v0.29.0 | ||||
| 	golang.org/x/text v0.21.0 | ||||
| 	golang.org/x/crypto v0.36.0 | ||||
| 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 | ||||
| 	golang.org/x/sys v0.31.0 | ||||
| 	golang.org/x/text v0.23.0 | ||||
| 	gopkg.in/yaml.v3 v3.0.1 | ||||
| 	modernc.org/sqlite v1.25.0 | ||||
| 	mvdan.cc/sh/v3 v3.10.0 | ||||
| @@ -45,7 +51,8 @@ require ( | ||||
| 	github.com/Masterminds/semver/v3 v3.3.0 // indirect | ||||
| 	github.com/Masterminds/sprig/v3 v3.2.3 // indirect | ||||
| 	github.com/Microsoft/go-winio v0.6.1 // indirect | ||||
| 	github.com/ProtonMail/go-crypto v1.0.0 // indirect | ||||
| 	github.com/ProtonMail/go-crypto v1.1.3 // indirect | ||||
| 	github.com/alecthomas/repr v0.2.0 // indirect | ||||
| 	github.com/andybalholm/brotli v1.0.4 // indirect | ||||
| 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | ||||
| 	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect | ||||
| @@ -59,24 +66,31 @@ require ( | ||||
| 	github.com/cloudflare/circl v1.3.8 // indirect | ||||
| 	github.com/connesc/cipherio v0.2.1 // indirect | ||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect | ||||
| 	github.com/cyphar/filepath-securejoin v0.2.4 // indirect | ||||
| 	github.com/creack/pty v1.1.24 // indirect | ||||
| 	github.com/cyphar/filepath-securejoin v0.2.5 // indirect | ||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||
| 	github.com/dlclark/regexp2 v1.10.0 // indirect | ||||
| 	github.com/dsnet/compress v0.0.1 // indirect | ||||
| 	github.com/dustin/go-humanize v1.0.1 // indirect | ||||
| 	github.com/efficientgo/core v1.0.0-rc.0 // indirect | ||||
| 	github.com/emirpasic/gods v1.18.1 // indirect | ||||
| 	github.com/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 | ||||
| 	github.com/google/uuid v1.4.0 // indirect | ||||
| 	github.com/goreleaser/chglog v0.6.1 // indirect | ||||
| 	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 | ||||
| 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | ||||
| @@ -95,6 +109,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 | ||||
| @@ -103,7 +118,7 @@ require ( | ||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||
| 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect | ||||
| 	github.com/shopspring/decimal v1.2.0 // indirect | ||||
| 	github.com/skeema/knownhosts v1.2.2 // indirect | ||||
| 	github.com/skeema/knownhosts v1.3.0 // indirect | ||||
| 	github.com/spf13/cast v1.6.0 // indirect | ||||
| 	github.com/therootcompany/xz v1.0.1 // indirect | ||||
| 	github.com/ulikunitz/xz v0.5.12 // indirect | ||||
| @@ -112,11 +127,14 @@ require ( | ||||
| 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect | ||||
| 	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/sync v0.10.0 // indirect | ||||
| 	golang.org/x/term v0.28.0 // indirect | ||||
| 	golang.org/x/tools v0.22.0 // indirect | ||||
| 	golang.org/x/mod v0.19.0 // indirect | ||||
| 	golang.org/x/net v0.38.0 // indirect | ||||
| 	golang.org/x/sync v0.12.0 // indirect | ||||
| 	golang.org/x/term v0.30.0 // indirect | ||||
| 	golang.org/x/tools v0.23.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 | ||||
|   | ||||
							
								
								
									
										153
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								go.sum
									
									
									
									
									
								
							| @@ -17,8 +17,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo | ||||
| dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= | ||||
| dario.cat/mergo v1.0.1/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= | ||||
| gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8= | ||||
| gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/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= | ||||
| @@ -39,8 +39,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc | ||||
| github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= | ||||
| github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= | ||||
| github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= | ||||
| github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= | ||||
| github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= | ||||
| github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= | ||||
| github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= | ||||
| github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= | ||||
| github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= | ||||
| github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s= | ||||
| @@ -61,6 +61,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd | ||||
| github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= | ||||
| github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= | ||||
| github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= | ||||
| github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= | ||||
| github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= | ||||
| github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= | ||||
| github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= | ||||
| github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= | ||||
| @@ -69,12 +71,17 @@ 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/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= | ||||
| 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/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs= | ||||
| github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= | ||||
| github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= | ||||
| github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= | ||||
| github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= | ||||
| github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= | ||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||||
| github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= | ||||
| github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||
| github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= | ||||
| github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= | ||||
| github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= | ||||
| @@ -93,7 +100,6 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR | ||||
| github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | ||||
| github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= | ||||
| github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||||
| github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= | ||||
| github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI= | ||||
| github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= | ||||
| github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= | ||||
| @@ -101,10 +107,10 @@ github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoV | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.4/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/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= | ||||
| github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= | ||||
| github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= | ||||
| github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= | ||||
| github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= | ||||
| github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| @@ -115,26 +121,32 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh | ||||
| github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= | ||||
| github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= | ||||
| github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= | ||||
| github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= | ||||
| github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= | ||||
| github.com/efficientgo/core v1.0.0-rc.0 h1:jJoA0N+C4/knWYVZ6GrdHOtDyrg8Y/TR4vFpTaqTsqs= | ||||
| github.com/efficientgo/core v1.0.0-rc.0/go.mod h1:kQa0V74HNYMfuJH6jiPiwNdpWXl4xd/K4tzlrcvYDQI= | ||||
| github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 h1:C/FNIs+MtAJgQYLJ9FX/ACFYyDRuLYoXTmueErrOJyA= | ||||
| github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0/go.mod h1:plsKU0YHE9uX+7utvr7SiDtVBSHJyEfHRO4UnUgDmts= | ||||
| github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= | ||||
| github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= | ||||
| github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= | ||||
| github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= | ||||
| github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||
| github.com/envoyproxy/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= | ||||
| github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= | ||||
| github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= | ||||
| github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= | ||||
| github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= | ||||
| github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= | ||||
| github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= | ||||
| github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= | ||||
| github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= | ||||
| github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= | ||||
| github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= | ||||
| github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= | ||||
| github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= | ||||
| github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= | ||||
| github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= | ||||
| github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= | ||||
| github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= | ||||
| github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||||
| github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= | ||||
| @@ -161,6 +173,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.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= | ||||
| @@ -169,8 +184,11 @@ 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= | ||||
| github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= | ||||
| github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= | ||||
| github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= | ||||
| github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= | ||||
| @@ -197,10 +215,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= | ||||
| @@ -215,8 +239,12 @@ 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= | ||||
| github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= | ||||
| github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | ||||
| github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= | ||||
| github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | ||||
| @@ -248,9 +276,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= | ||||
| @@ -262,6 +292,8 @@ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh | ||||
| github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= | ||||
| github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= | ||||
| github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= | ||||
| github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= | ||||
| github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | ||||
| github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= | ||||
| github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= | ||||
| github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= | ||||
| @@ -282,10 +314,14 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= | ||||
| github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= | ||||
| github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= | ||||
| github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= | ||||
| github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= | ||||
| github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | ||||
| github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk= | ||||
| github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= | ||||
| github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= | ||||
| github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= | ||||
| 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.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= | ||||
| github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= | ||||
| github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= | ||||
| github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= | ||||
| github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= | ||||
| @@ -296,7 +332,15 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= | ||||
| github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= | ||||
| github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= | ||||
| github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/prometheus/common v0.36.0 h1:78hJTing+BLYLjhXE+Z2BubeEymH5Lr0/Mt8FKkxxYo= | ||||
| github.com/prometheus/common v0.36.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= | ||||
| github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= | ||||
| github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= | ||||
| github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= | ||||
| github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= | ||||
| github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= | ||||
| @@ -317,8 +361,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG | ||||
| github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= | ||||
| github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= | ||||
| github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | ||||
| github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= | ||||
| github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= | ||||
| github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= | ||||
| github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= | ||||
| github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= | ||||
| github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= | ||||
| github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= | ||||
| @@ -338,6 +382,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO | ||||
| github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||
| github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns= | ||||
| github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ= | ||||
| github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= | ||||
| github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= | ||||
| github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= | ||||
| @@ -373,10 +419,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U | ||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= | ||||
| golang.org/x/crypto v0.3.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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= | ||||
| golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= | ||||
| 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= | ||||
| @@ -385,8 +429,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE | ||||
| golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||||
| golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||||
| golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | ||||
| golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= | ||||
| golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= | ||||
| golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= | ||||
| golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= | ||||
| golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||||
| golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||
| @@ -405,9 +449,8 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= | ||||
| golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||||
| golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||
| golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||
| golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= | ||||
| golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||
| golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= | ||||
| golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| @@ -426,15 +469,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v | ||||
| golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| 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.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= | ||||
| golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= | ||||
| 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.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= | ||||
| @@ -442,9 +485,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ | ||||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= | ||||
| golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||
| golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= | ||||
| golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | ||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| @@ -455,6 +497,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= | ||||
| @@ -470,18 +513,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= | ||||
| golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= | ||||
| golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= | ||||
| golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | ||||
| golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= | ||||
| golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= | ||||
| golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= | ||||
| golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= | ||||
| golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= | ||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| @@ -490,10 +529,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||
| golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= | ||||
| golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= | ||||
| golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= | ||||
| golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= | ||||
| golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= | ||||
| golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| @@ -521,9 +558,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK | ||||
| golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||
| golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | ||||
| golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= | ||||
| golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= | ||||
| golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= | ||||
| golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= | ||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| @@ -540,6 +576,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.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= | ||||
| @@ -553,6 +591,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= | ||||
| @@ -560,6 +600,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/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= | ||||
| @@ -570,6 +616,7 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= | ||||
| gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= | ||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||||
| gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
|   | ||||
| @@ -30,6 +30,7 @@ import ( | ||||
| 	"mvdan.cc/sh/v3/expand" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||
| @@ -71,19 +72,17 @@ func HelperCmd() *cli.Command { | ||||
| 			helper, ok := helpers.Helpers[c.Args().First()] | ||||
| 			if !ok { | ||||
| 				slog.Error(gotext.Get("No such helper command"), "name", c.Args().First()) | ||||
| 				os.Exit(1) | ||||
| 				return cli.Exit(gotext.Get("No such helper command"), 1) | ||||
| 			} | ||||
|  | ||||
| 			wd, err := os.Getwd() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error getting working directory"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) | ||||
| 			} | ||||
|  | ||||
| 			info, err := distro.ParseOSRelease(ctx) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error getting working directory"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) | ||||
| 			} | ||||
|  | ||||
| 			hc := interp.HandlerContext{ | ||||
|   | ||||
							
								
								
									
										94
									
								
								info.go
									
									
									
									
									
								
							
							
						
						
									
										94
									
								
								info.go
									
									
									
									
									
								
							| @@ -21,7 +21,6 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/jeandeaual/go-locale" | ||||
| @@ -30,11 +29,11 @@ import ( | ||||
| 	"gopkg.in/yaml.v3" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||
| 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||
| ) | ||||
|  | ||||
| func InfoCmd() *cli.Command { | ||||
| @@ -48,20 +47,25 @@ func InfoCmd() *cli.Command { | ||||
| 				Usage:   gotext.Get("Show all information, not just for the current distro"), | ||||
| 			}, | ||||
| 		}, | ||||
| 		BashComplete: func(c *cli.Context) { | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			err := db.Init(ctx) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			result, err := db.GetPkgs(c.Context, "true") | ||||
| 			ctx := c.Context | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error getting packages"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			result, err := deps.DB.GetPkgs(c.Context, "true") | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||
| 			} | ||||
| 			defer result.Close() | ||||
|  | ||||
| @@ -69,47 +73,45 @@ func InfoCmd() *cli.Command { | ||||
| 				var pkg database.Package | ||||
| 				err = result.StructScan(&pkg) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error iterating over packages"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) | ||||
| 				} | ||||
|  | ||||
| 				fmt.Println(pkg.Name) | ||||
| 			} | ||||
| 		}, | ||||
| 			return nil | ||||
| 		}), | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			err := db.Init(ctx) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			rs := repos.New(cfg, db) | ||||
|  | ||||
| 			args := c.Args() | ||||
| 			if args.Len() < 1 { | ||||
| 				slog.Error(gotext.Get("Command info expected at least 1 argument, got %d", args.Len())) | ||||
| 				os.Exit(1) | ||||
| 				return cli.Exit(gotext.Get("Command info expected at least 1 argument, got %d", args.Len()), 1) | ||||
| 			} | ||||
|  | ||||
| 			if cfg.AutoPull(ctx) { | ||||
| 				err := rs.Pull(ctx, cfg.Repos(ctx)) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 				} | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithRepos(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return cli.Exit(err, 1) | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			rs := deps.Repos | ||||
|  | ||||
| 			found, _, err := rs.FindPkgs(ctx, args.Slice()) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error finding packages"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error finding packages"), err) | ||||
| 			} | ||||
|  | ||||
| 			if len(found) == 0 { | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Package not found"), err) | ||||
| 			} | ||||
|  | ||||
| 			pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive")) | ||||
| @@ -119,15 +121,16 @@ func InfoCmd() *cli.Command { | ||||
|  | ||||
| 			systemLang, err := locale.GetLanguage() | ||||
| 			if err != nil { | ||||
| 				slog.Error("Can't detect system language", "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Can't detect system language"), err) | ||||
| 			} | ||||
| 			if systemLang == "" { | ||||
| 				systemLang = "en" | ||||
| 			} | ||||
|  | ||||
| 			if !all { | ||||
| 				info, err := distro.ParseOSRelease(ctx) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error parsing os-release file"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) | ||||
| 				} | ||||
| 				names, err = overrides.Resolve( | ||||
| 					info, | ||||
| @@ -135,8 +138,7 @@ func InfoCmd() *cli.Command { | ||||
| 						WithLanguages([]string{systemLang}), | ||||
| 				) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error resolving overrides"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @@ -144,14 +146,12 @@ func InfoCmd() *cli.Command { | ||||
| 				if !all { | ||||
| 					err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names)) | ||||
| 					if err != nil { | ||||
| 						slog.Error(gotext.Get("Error encoding script variables"), "err", err) | ||||
| 						os.Exit(1) | ||||
| 						return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) | ||||
| 					} | ||||
| 				} else { | ||||
| 					err = yaml.NewEncoder(os.Stdout).Encode(pkg) | ||||
| 					if err != nil { | ||||
| 						slog.Error(gotext.Get("Error encoding script variables"), "err", err) | ||||
| 						os.Exit(1) | ||||
| 						return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
|   | ||||
							
								
								
									
										217
									
								
								install.go
									
									
									
									
									
								
							
							
						
						
									
										217
									
								
								install.go
									
									
									
									
									
								
							| @@ -21,20 +21,17 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
|  | ||||
| 	"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" | ||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||
| 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/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" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||
| ) | ||||
|  | ||||
| func InstallCmd() *cli.Command { | ||||
| @@ -50,84 +47,98 @@ func InstallCmd() *cli.Command { | ||||
| 			}, | ||||
| 		}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
| 			if err := utils.ExitIfNotRoot(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			args := c.Args() | ||||
| 			if args.Len() < 1 { | ||||
| 				slog.Error(gotext.Get("Command install expected at least 1 argument, got %d", args.Len())) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil) | ||||
| 			} | ||||
|  | ||||
| 			mgr := manager.Detect() | ||||
| 			if mgr == nil { | ||||
| 				slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) | ||||
| 				os.Exit(1) | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			rs := repos.New(cfg, db) | ||||
| 			err := db.Init(ctx) | ||||
| 			installer, installerClose, err := build.GetSafeInstaller() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
| 			defer installerClose() | ||||
|  | ||||
| 			if err := utils.ExitIfCantSetNoNewPrivs(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			if cfg.AutoPull(ctx) { | ||||
| 				err := rs.Pull(ctx, cfg.Repos(ctx)) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error pulling repositories"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			found, notFound, err := rs.FindPkgs(ctx, args.Slice()) | ||||
| 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error finding packages"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
| 			defer scripterClose() | ||||
|  | ||||
| 			pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive")) | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 			opts := types.BuildOpts{ | ||||
| 				Manager:     mgr, | ||||
| 				Clean:       c.Bool("clean"), | ||||
| 				Interactive: c.Bool("interactive"), | ||||
| 			} | ||||
|  | ||||
| 			info, err := distro.ParseOSRelease(ctx) | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithRepos(). | ||||
| 				WithDistroInfo(). | ||||
| 				WithManager(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error parsing os release"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			builder := build.NewBuilder( | ||||
| 				ctx, | ||||
| 				opts, | ||||
| 				rs, | ||||
| 				info, | ||||
| 				cfg, | ||||
| 			builder, err := build.NewMainBuilder( | ||||
| 				deps.Cfg, | ||||
| 				deps.Manager, | ||||
| 				deps.Repos, | ||||
| 				scripter, | ||||
| 				installer, | ||||
| 			) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = builder.InstallPkgs( | ||||
| 				ctx, | ||||
| 				&build.BuildArgs{ | ||||
| 					Opts: &types.BuildOpts{ | ||||
| 						Clean:       c.Bool("clean"), | ||||
| 						Interactive: c.Bool("interactive"), | ||||
| 					}, | ||||
| 					Info:       deps.Info, | ||||
| 					PkgFormat_: build.GetPkgFormat(deps.Manager), | ||||
| 				}, | ||||
| 				args.Slice(), | ||||
| 			) | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error parsing os release"), err) | ||||
| 			} | ||||
|  | ||||
| 			builder.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{ | ||||
| 				Manager:     mgr, | ||||
| 				Clean:       c.Bool("clean"), | ||||
| 				Interactive: c.Bool("interactive"), | ||||
| 			}) | ||||
| 			return nil | ||||
| 		}, | ||||
| 		BashComplete: func(c *cli.Context) { | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			err := db.Init(c.Context) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			result, err := db.GetPkgs(c.Context, "true") | ||||
|  | ||||
| 			ctx := c.Context | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error getting packages"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			result, err := deps.DB.GetPkgs(c.Context, "true") | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||
| 			} | ||||
| 			defer result.Close() | ||||
|  | ||||
| @@ -135,13 +146,14 @@ func InstallCmd() *cli.Command { | ||||
| 				var pkg database.Package | ||||
| 				err = result.StructScan(&pkg) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error iterating over packages"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) | ||||
| 				} | ||||
|  | ||||
| 				fmt.Println(pkg.Name) | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 			return nil | ||||
| 		}), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -150,23 +162,80 @@ func RemoveCmd() *cli.Command { | ||||
| 		Name:    "remove", | ||||
| 		Usage:   gotext.Get("Remove an installed package"), | ||||
| 		Aliases: []string{"rm"}, | ||||
| 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithManager(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return cli.Exit(err, 1) | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			installedAlrPackages := map[string]string{} | ||||
| 			installed, err := deps.Manager.ListInstalled(&manager.Opts{}) | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error listing installed packages"), err) | ||||
| 			} | ||||
| 			for pkgName, version := range installed { | ||||
| 				matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName) | ||||
| 				if matches != nil { | ||||
| 					packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")] | ||||
| 					repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")] | ||||
| 					installedAlrPackages[fmt.Sprintf("%s/%s", repoName, packageName)] = version | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			result, err := deps.DB.GetPkgs(c.Context, "true") | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||
| 			} | ||||
| 			defer result.Close() | ||||
|  | ||||
| 			for result.Next() { | ||||
| 				var pkg database.Package | ||||
| 				err = result.StructScan(&pkg) | ||||
| 				if err != nil { | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) | ||||
| 				} | ||||
|  | ||||
| 				_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] | ||||
| 				if !ok { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				fmt.Println(pkg.Name) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}), | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			if err := utils.ExitIfNotRoot(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			args := c.Args() | ||||
| 			if args.Len() < 1 { | ||||
| 				slog.Error(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len())) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len()), nil) | ||||
| 			} | ||||
|  | ||||
| 			mgr := manager.Detect() | ||||
| 			if mgr == nil { | ||||
| 				slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			err := mgr.Remove(nil, c.Args().Slice()...) | ||||
| 			deps, err := appbuilder. | ||||
| 				New(c.Context). | ||||
| 				WithManager(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error removing packages"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			if err := deps.Manager.Remove(&manager.Opts{ | ||||
| 				NoConfirm: !c.Bool("interactive"), | ||||
| 			}, c.Args().Slice()...); err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error removing packages"), err) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
|   | ||||
							
								
								
									
										280
									
								
								internal.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								internal.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,280 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"os/user" | ||||
| 	"path/filepath" | ||||
| 	"syscall" | ||||
|  | ||||
| 	"github.com/hashicorp/go-hclog" | ||||
| 	"github.com/hashicorp/go-plugin" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/urfave/cli/v2" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||
| ) | ||||
|  | ||||
| func InternalBuildCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:     "_internal-safe-script-executor", | ||||
| 		HideHelp: true, | ||||
| 		Hidden:   true, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			logger.SetupForGoPlugin() | ||||
|  | ||||
| 			slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid()) | ||||
|  | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			cfg := config.New() | ||||
| 			err := cfg.Load() | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error loading config"), err) | ||||
| 			} | ||||
|  | ||||
| 			logger := hclog.New(&hclog.LoggerOptions{ | ||||
| 				Name:        "plugin", | ||||
| 				Output:      os.Stderr, | ||||
| 				Level:       hclog.Debug, | ||||
| 				JSONFormat:  false, | ||||
| 				DisableTime: true, | ||||
| 			}) | ||||
|  | ||||
| 			plugin.Serve(&plugin.ServeConfig{ | ||||
| 				HandshakeConfig: build.HandshakeConfig, | ||||
| 				Plugins: map[string]plugin.Plugin{ | ||||
| 					"script-executor": &build.ScriptExecutorPlugin{ | ||||
| 						Impl: build.NewLocalScriptExecutor(cfg), | ||||
| 					}, | ||||
| 				}, | ||||
| 				Logger: logger, | ||||
| 			}) | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func InternalInstallCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:     "_internal-installer", | ||||
| 		HideHelp: true, | ||||
| 		Hidden:   true, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			logger.SetupForGoPlugin() | ||||
|  | ||||
| 			if err := utils.EnsureIsAlrUser(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			// Before escalating the rights, we made sure that | ||||
| 			// this is an ALR user, so it looks safe. | ||||
| 			err := utils.EscalateToRootUid() | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit("cannot escalate to root", err) | ||||
| 			} | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(c.Context). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithReposNoPull(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			logger := hclog.New(&hclog.LoggerOptions{ | ||||
| 				Name:        "plugin", | ||||
| 				Output:      os.Stderr, | ||||
| 				Level:       hclog.Trace, | ||||
| 				JSONFormat:  true, | ||||
| 				DisableTime: true, | ||||
| 			}) | ||||
|  | ||||
| 			plugin.Serve(&plugin.ServeConfig{ | ||||
| 				HandshakeConfig: build.HandshakeConfig, | ||||
| 				Plugins: map[string]plugin.Plugin{ | ||||
| 					"installer": &build.InstallerPlugin{ | ||||
| 						Impl: build.NewInstaller( | ||||
| 							manager.Detect(), | ||||
| 						), | ||||
| 					}, | ||||
| 				}, | ||||
| 				Logger: logger, | ||||
| 			}) | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func Mount(target string) (string, func(), error) { | ||||
| 	exe, err := os.Executable() | ||||
| 	if err != nil { | ||||
| 		return "", nil, fmt.Errorf("failed to get executable path: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	cmd := exec.Command(exe, "_internal-temporary-mount", target) | ||||
|  | ||||
| 	stdoutPipe, err := cmd.StdoutPipe() | ||||
| 	if err != nil { | ||||
| 		return "", nil, fmt.Errorf("failed to get stdout pipe: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	stdinPipe, err := cmd.StdinPipe() | ||||
| 	if err != nil { | ||||
| 		return "", nil, fmt.Errorf("failed to get stdin pipe: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	cmd.Stderr = os.Stderr | ||||
|  | ||||
| 	if err := cmd.Start(); err != nil { | ||||
| 		return "", nil, fmt.Errorf("failed to start mount: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	scanner := bufio.NewScanner(stdoutPipe) | ||||
| 	var mountPath string | ||||
| 	if scanner.Scan() { | ||||
| 		mountPath = scanner.Text() | ||||
| 	} | ||||
|  | ||||
| 	if err := scanner.Err(); err != nil { | ||||
| 		_ = cmd.Process.Kill() | ||||
| 		return "", nil, fmt.Errorf("failed to read mount output: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if mountPath == "" { | ||||
| 		_ = cmd.Process.Kill() | ||||
| 		return "", nil, errors.New("mount failed: no target path returned") | ||||
| 	} | ||||
|  | ||||
| 	cleanup := func() { | ||||
| 		slog.Debug("cleanup triggered") | ||||
| 		_, _ = fmt.Fprintln(stdinPipe, "") | ||||
| 		_ = cmd.Wait() | ||||
| 	} | ||||
|  | ||||
| 	return mountPath, cleanup, nil | ||||
| } | ||||
|  | ||||
| func InternalMountCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:     "_internal-temporary-mount", | ||||
| 		HideHelp: true, | ||||
| 		Hidden:   true, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			logger.SetupForGoPlugin() | ||||
|  | ||||
| 			sourceDir := c.Args().First() | ||||
|  | ||||
| 			u, err := user.Current() | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit("cannot get current user", err) | ||||
| 			} | ||||
|  | ||||
| 			_, alrGid, err := utils.GetUidGidAlrUser() | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit("cannot get alr user", err) | ||||
| 			} | ||||
|  | ||||
| 			if _, err := os.Stat(sourceDir); err != nil { | ||||
| 				return cliutils.FormatCliExit(fmt.Sprintf("cannot read %s", sourceDir), err) | ||||
| 			} | ||||
|  | ||||
| 			if err := utils.EnuseIsPrivilegedGroupMember(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			// Before escalating the rights, we made sure that | ||||
| 			// 1. user in wheel group | ||||
| 			// 2. user can access sourceDir | ||||
| 			if err := utils.EscalateToRootUid(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			if err := syscall.Setgid(alrGid); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			if err := os.MkdirAll(constants.AlrRunDir, 0o770); err != nil { | ||||
| 				return cliutils.FormatCliExit(fmt.Sprintf("failed to create %s", constants.AlrRunDir), err) | ||||
| 			} | ||||
|  | ||||
| 			if err := os.Chown(constants.AlrRunDir, 0, alrGid); err != nil { | ||||
| 				return cliutils.FormatCliExit(fmt.Sprintf("failed to chown %s", constants.AlrRunDir), err) | ||||
| 			} | ||||
|  | ||||
| 			targetDir := filepath.Join(constants.AlrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid())) | ||||
| 			// 0750: owner (root) and group (alr) | ||||
| 			if err := os.MkdirAll(targetDir, 0o750); err != nil { | ||||
| 				return cliutils.FormatCliExit("error creating bindfs target directory", err) | ||||
| 			} | ||||
|  | ||||
| 			//  chown AlrRunDir/mounts/bindfs-* to (root:alr), | ||||
| 			//  so alr user can access dir | ||||
| 			if err := os.Chown(targetDir, 0, alrGid); err != nil { | ||||
| 				return cliutils.FormatCliExit("failed to chown bindfs directory", err) | ||||
| 			} | ||||
|  | ||||
| 			bindfsCmd := exec.Command( | ||||
| 				"bindfs", | ||||
| 				fmt.Sprintf("--map=%s/alr:@%s/@alr", u.Uid, u.Gid), | ||||
| 				sourceDir, | ||||
| 				targetDir, | ||||
| 			) | ||||
|  | ||||
| 			bindfsCmd.Stderr = os.Stderr | ||||
|  | ||||
| 			if err := bindfsCmd.Run(); err != nil { | ||||
| 				return cliutils.FormatCliExit("failed to strart bindfs", err) | ||||
| 			} | ||||
|  | ||||
| 			fmt.Println(targetDir) | ||||
|  | ||||
| 			_, _ = bufio.NewReader(os.Stdin).ReadString('\n') | ||||
|  | ||||
| 			slog.Debug("start unmount", "dir", targetDir) | ||||
|  | ||||
| 			umountCmd := exec.Command("umount", targetDir) | ||||
| 			umountCmd.Stderr = os.Stderr | ||||
| 			if err := umountCmd.Run(); err != nil { | ||||
| 				return cliutils.FormatCliExit(fmt.Sprintf("failed to unmount %s", targetDir), err) | ||||
| 			} | ||||
|  | ||||
| 			if err := os.Remove(targetDir); err != nil { | ||||
| 				return cliutils.FormatCliExit(fmt.Sprintf("error removing directory %s", targetDir), err) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										176
									
								
								internal/cliutils/app_builder/builder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								internal/cliutils/app_builder/builder.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,176 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package appbuilder | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"log/slog" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||
| ) | ||||
|  | ||||
| type AppDeps struct { | ||||
| 	Cfg     *config.ALRConfig | ||||
| 	DB      *db.Database | ||||
| 	Repos   *repos.Repos | ||||
| 	Info    *distro.OSRelease | ||||
| 	Manager manager.Manager | ||||
| } | ||||
|  | ||||
| func (d *AppDeps) Defer() { | ||||
| 	if d.DB != nil { | ||||
| 		if err := d.DB.Close(); err != nil { | ||||
| 			slog.Warn("failed to close db", "err", err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type AppBuilder struct { | ||||
| 	deps AppDeps | ||||
| 	err  error | ||||
| 	ctx  context.Context | ||||
| } | ||||
|  | ||||
| func New(ctx context.Context) *AppBuilder { | ||||
| 	return &AppBuilder{ctx: ctx} | ||||
| } | ||||
|  | ||||
| func (b *AppBuilder) UseConfig(cfg *config.ALRConfig) *AppBuilder { | ||||
| 	if b.err != nil { | ||||
| 		return b | ||||
| 	} | ||||
| 	b.deps.Cfg = cfg | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *AppBuilder) WithConfig() *AppBuilder { | ||||
| 	if b.err != nil { | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	cfg := config.New() | ||||
| 	if err := cfg.Load(); err != nil { | ||||
| 		b.err = cliutils.FormatCliExit(gotext.Get("Error loading config"), err) | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	b.deps.Cfg = cfg | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *AppBuilder) WithDB() *AppBuilder { | ||||
| 	if b.err != nil { | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	cfg := b.deps.Cfg | ||||
| 	if cfg == nil { | ||||
| 		b.err = errors.New("config is required before initializing DB") | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	db := db.New(cfg) | ||||
| 	if err := db.Init(b.ctx); err != nil { | ||||
| 		b.err = cliutils.FormatCliExit(gotext.Get("Error initialization database"), err) | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	b.deps.DB = db | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *AppBuilder) WithRepos() *AppBuilder { | ||||
| 	b.withRepos(true, false) | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *AppBuilder) WithReposForcePull() *AppBuilder { | ||||
| 	b.withRepos(true, true) | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *AppBuilder) WithReposNoPull() *AppBuilder { | ||||
| 	b.withRepos(false, false) | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder { | ||||
| 	if b.err != nil { | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	cfg := b.deps.Cfg | ||||
| 	db := b.deps.DB | ||||
| 	if cfg == nil || db == nil { | ||||
| 		b.err = errors.New("config and db are required before initializing repos") | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	rs := repos.New(cfg, db) | ||||
|  | ||||
| 	if enablePull && (forcePull || cfg.AutoPull()) { | ||||
| 		if err := rs.Pull(b.ctx, cfg.Repos()); err != nil { | ||||
| 			b.err = cliutils.FormatCliExit(gotext.Get("Error pulling repositories"), err) | ||||
| 			return b | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	b.deps.Repos = rs | ||||
|  | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *AppBuilder) WithDistroInfo() *AppBuilder { | ||||
| 	if b.err != nil { | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	b.deps.Info, b.err = distro.ParseOSRelease(b.ctx) | ||||
| 	if b.err != nil { | ||||
| 		b.err = cliutils.FormatCliExit(gotext.Get("Error parsing os release"), b.err) | ||||
| 	} | ||||
|  | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *AppBuilder) WithManager() *AppBuilder { | ||||
| 	if b.err != nil { | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	b.deps.Manager = manager.Detect() | ||||
| 	if b.deps.Manager == nil { | ||||
| 		b.err = cliutils.FormatCliExit(gotext.Get("Unable to detect a supported package manager on the system"), nil) | ||||
| 	} | ||||
|  | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *AppBuilder) Build() (*AppDeps, error) { | ||||
| 	if b.err != nil { | ||||
| 		return nil, b.err | ||||
| 	} | ||||
| 	return &b.deps, nil | ||||
| } | ||||
							
								
								
									
										63
									
								
								internal/cliutils/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								internal/cliutils/utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package cliutils | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
|  | ||||
| 	"github.com/urfave/cli/v2" | ||||
| ) | ||||
|  | ||||
| type BashCompleteWithErrorFunc func(c *cli.Context) error | ||||
|  | ||||
| func BashCompleteWithError(f BashCompleteWithErrorFunc) cli.BashCompleteFunc { | ||||
| 	return func(c *cli.Context) { HandleExitCoder(f(c)) } | ||||
| } | ||||
|  | ||||
| func HandleExitCoder(err error) { | ||||
| 	if err == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if exitErr, ok := err.(cli.ExitCoder); ok { | ||||
| 		if err.Error() != "" { | ||||
| 			if _, ok := exitErr.(cli.ErrorFormatter); ok { | ||||
| 				slog.Error(fmt.Sprintf("%+v\n", err)) | ||||
| 			} else { | ||||
| 				slog.Error(err.Error()) | ||||
| 			} | ||||
| 		} | ||||
| 		cli.OsExiter(exitErr.ExitCode()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	slog.Error(err.Error()) | ||||
| 	cli.OsExiter(1) | ||||
| } | ||||
|  | ||||
| func FormatCliExit(msg string, err error) cli.ExitCoder { | ||||
| 	return FormatCliExitWithCode(msg, err, 1) | ||||
| } | ||||
|  | ||||
| func FormatCliExitWithCode(msg string, err error, exitCode int) cli.ExitCoder { | ||||
| 	if err == nil { | ||||
| 		return cli.Exit(errors.New(msg), exitCode) | ||||
| 	} | ||||
| 	return cli.Exit(fmt.Errorf("%s: %w", msg, err), exitCode) | ||||
| } | ||||
| @@ -20,32 +20,28 @@ | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"sync" | ||||
| 	"reflect" | ||||
|  | ||||
| 	"github.com/caarlos0/env" | ||||
| 	"github.com/pelletier/go-toml/v2" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| ) | ||||
|  | ||||
| type ALRConfig struct { | ||||
| 	cfg   *types.Config | ||||
| 	paths *Paths | ||||
|  | ||||
| 	cfgOnce   sync.Once | ||||
| 	pathsOnce sync.Once | ||||
| } | ||||
|  | ||||
| var defaultConfig = &types.Config{ | ||||
| 	RootCmd:          "sudo", | ||||
| 	PagerStyle:       "native", | ||||
| 	IgnorePkgUpdates: []string{}, | ||||
| 	AutoPull:         false, | ||||
| 	AutoPull:         true, | ||||
| 	Repos:            []types.Repo{}, | ||||
| } | ||||
|  | ||||
| @@ -53,147 +49,108 @@ func New() *ALRConfig { | ||||
| 	return &ALRConfig{} | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) Load(ctx context.Context) { | ||||
| 	cfgFl, err := os.Open(c.GetPaths(ctx).ConfigPath) | ||||
| func readConfig(path string) (*types.Config, error) { | ||||
| 	file, err := os.Open(path) | ||||
| 	if err != nil { | ||||
| 		slog.Warn(gotext.Get("Error opening config file, using defaults"), "err", err) | ||||
| 		c.cfg = defaultConfig | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer file.Close() | ||||
|  | ||||
| 	config := types.Config{} | ||||
|  | ||||
| 	if err := toml.NewDecoder(file).Decode(&config); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &config, nil | ||||
| } | ||||
|  | ||||
| func mergeStructs(dst, src interface{}) { | ||||
| 	srcVal := reflect.ValueOf(src) | ||||
| 	if srcVal.IsNil() { | ||||
| 		return | ||||
| 	} | ||||
| 	defer cfgFl.Close() | ||||
| 	srcVal = srcVal.Elem() | ||||
| 	dstVal := reflect.ValueOf(dst).Elem() | ||||
|  | ||||
| 	// Copy the default configuration into config | ||||
| 	defCopy := *defaultConfig | ||||
| 	config := &defCopy | ||||
| 	config.Repos = nil | ||||
| 	for i := range srcVal.NumField() { | ||||
| 		srcField := srcVal.Field(i) | ||||
| 		srcFieldName := srcVal.Type().Field(i).Name | ||||
|  | ||||
| 	err = toml.NewDecoder(cfgFl).Decode(config) | ||||
| 	if err != nil { | ||||
| 		slog.Warn(gotext.Get("Error decoding config file, using defaults"), "err", err) | ||||
| 		c.cfg = defaultConfig | ||||
| 		return | ||||
| 		dstField := dstVal.FieldByName(srcFieldName) | ||||
| 		if dstField.IsValid() && dstField.CanSet() { | ||||
| 			dstField.Set(srcField) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) Load() error { | ||||
| 	systemConfig, err := readConfig( | ||||
| 		constants.SystemConfigPath, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		slog.Debug("Cannot read system config", "err", err) | ||||
| 	} | ||||
|  | ||||
| 	config := &types.Config{} | ||||
|  | ||||
| 	mergeStructs(config, defaultConfig) | ||||
| 	mergeStructs(config, systemConfig) | ||||
| 	err = env.Parse(config) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	c.cfg = config | ||||
|  | ||||
| 	c.paths = &Paths{} | ||||
| 	c.paths.UserConfigPath = constants.SystemConfigPath | ||||
| 	c.paths.CacheDir = constants.SystemCachePath | ||||
| 	c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo") | ||||
| 	c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs") | ||||
| 	c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db") | ||||
| 	// c.initPaths() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) initPaths() { | ||||
| 	paths := &Paths{} | ||||
|  | ||||
| 	cfgDir, err := os.UserConfigDir() | ||||
| 	if err != nil { | ||||
| 		slog.Error(gotext.Get("Unable to detect user config directory"), "err", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	paths.ConfigDir = filepath.Join(cfgDir, "alr") | ||||
|  | ||||
| 	err = os.MkdirAll(paths.ConfigDir, 0o755) | ||||
| 	if err != nil { | ||||
| 		slog.Error(gotext.Get("Unable to create ALR config directory"), "err", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	paths.ConfigPath = filepath.Join(paths.ConfigDir, "alr.toml") | ||||
|  | ||||
| 	if _, err := os.Stat(paths.ConfigPath); err != nil { | ||||
| 		cfgFl, err := os.Create(paths.ConfigPath) | ||||
| 		if err != nil { | ||||
| 			slog.Error(gotext.Get("Unable to create ALR config file"), "err", err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		err = toml.NewEncoder(cfgFl).Encode(&defaultConfig) | ||||
| 		if err != nil { | ||||
| 			slog.Error(gotext.Get("Error encoding default configuration"), "err", err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		cfgFl.Close() | ||||
| 	} | ||||
|  | ||||
| 	cacheDir, err := os.UserCacheDir() | ||||
| 	if err != nil { | ||||
| 		slog.Error(gotext.Get("Unable to detect cache directory"), "err", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	paths.CacheDir = filepath.Join(cacheDir, "alr") | ||||
| 	paths.RepoDir = filepath.Join(paths.CacheDir, "repo") | ||||
| 	paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs") | ||||
|  | ||||
| 	err = os.MkdirAll(paths.RepoDir, 0o755) | ||||
| 	if err != nil { | ||||
| 		slog.Error(gotext.Get("Unable to create repo cache directory"), "err", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	err = os.MkdirAll(paths.PkgsDir, 0o755) | ||||
| 	if err != nil { | ||||
| 		slog.Error(gotext.Get("Unable to create package cache directory"), "err", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	paths.DBPath = filepath.Join(paths.CacheDir, "db") | ||||
|  | ||||
| 	c.paths = paths | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) GetPaths(ctx context.Context) *Paths { | ||||
| 	c.pathsOnce.Do(func() { | ||||
| 		c.initPaths() | ||||
| 	}) | ||||
| 	return c.paths | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) Repos(ctx context.Context) []types.Repo { | ||||
| 	c.cfgOnce.Do(func() { | ||||
| 		c.Load(ctx) | ||||
| 	}) | ||||
| 	return c.cfg.Repos | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) SetRepos(ctx context.Context, repos []types.Repo) { | ||||
| 	c.cfgOnce.Do(func() { | ||||
| 		c.Load(ctx) | ||||
| 	}) | ||||
| 	c.cfg.Repos = repos | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) IgnorePkgUpdates(ctx context.Context) []string { | ||||
| 	c.cfgOnce.Do(func() { | ||||
| 		c.Load(ctx) | ||||
| 	}) | ||||
| 	return c.cfg.IgnorePkgUpdates | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) AutoPull(ctx context.Context) bool { | ||||
| 	c.cfgOnce.Do(func() { | ||||
| 		c.Load(ctx) | ||||
| 	}) | ||||
| 	return c.cfg.AutoPull | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) PagerStyle(ctx context.Context) string { | ||||
| 	c.cfgOnce.Do(func() { | ||||
| 		c.Load(ctx) | ||||
| 	}) | ||||
| 	return c.cfg.PagerStyle | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) AllowRunAsRoot(ctx context.Context) bool { | ||||
| 	c.cfgOnce.Do(func() { | ||||
| 		c.Load(ctx) | ||||
| 	}) | ||||
| 	return c.cfg.Unsafe.AllowRunAsRoot | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) RootCmd(ctx context.Context) string { | ||||
| 	c.cfgOnce.Do(func() { | ||||
| 		c.Load(ctx) | ||||
| 	}) | ||||
| func (c *ALRConfig) RootCmd() string { | ||||
| 	return c.cfg.RootCmd | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) Save(f *os.File) error { | ||||
| func (c *ALRConfig) PagerStyle() string { | ||||
| 	return c.cfg.PagerStyle | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) AutoPull() bool { | ||||
| 	return c.cfg.AutoPull | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) Repos() []types.Repo { | ||||
| 	return c.cfg.Repos | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) SetRepos(repos []types.Repo) { | ||||
| 	c.cfg.Repos = repos | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) IgnorePkgUpdates() []string { | ||||
| 	return c.cfg.IgnorePkgUpdates | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) LogLevel() string { | ||||
| 	return c.cfg.LogLevel | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) GetPaths() *Paths { | ||||
| 	return c.paths | ||||
| } | ||||
|  | ||||
| func (c *ALRConfig) SaveUserConfig() error { | ||||
| 	f, err := os.Create(c.paths.UserConfigPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return toml.NewEncoder(f).Encode(c.cfg) | ||||
| } | ||||
|   | ||||
| @@ -21,10 +21,9 @@ package config | ||||
|  | ||||
| // Paths contains various paths used by ALR | ||||
| type Paths struct { | ||||
| 	ConfigDir  string | ||||
| 	ConfigPath string | ||||
| 	CacheDir   string | ||||
| 	RepoDir    string | ||||
| 	PkgsDir    string | ||||
| 	DBPath     string | ||||
| 	UserConfigPath string | ||||
| 	CacheDir       string | ||||
| 	RepoDir        string | ||||
| 	PkgsDir        string | ||||
| 	DBPath         string | ||||
| } | ||||
|   | ||||
							
								
								
									
										24
									
								
								internal/constants/constants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								internal/constants/constants.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package constants | ||||
|  | ||||
| const ( | ||||
| 	SystemConfigPath = "/etc/alr/alr.toml" | ||||
| 	SystemCachePath  = "/var/cache/alr" | ||||
| 	AlrRunDir        = "/var/run/alr" | ||||
| 	PrivilegedGroup  = "wheel" | ||||
| ) | ||||
| @@ -31,7 +31,7 @@ import ( | ||||
|  | ||||
| // CurrentVersion is the current version of the database. | ||||
| // The database is reset if its version doesn't match this. | ||||
| const CurrentVersion = 3 | ||||
| const CurrentVersion = 4 | ||||
|  | ||||
| // Package is a ALR package's database representation | ||||
| type Package struct { | ||||
| @@ -40,7 +40,9 @@ type Package struct { | ||||
| 	Version       string                    `sh:"version,required" db:"version"` | ||||
| 	Release       int                       `sh:"release,required" db:"release"` | ||||
| 	Epoch         uint                      `sh:"epoch" db:"epoch"` | ||||
| 	Summary       JSON[map[string]string]   `db:"summary"` | ||||
| 	Description   JSON[map[string]string]   `db:"description"` | ||||
| 	Group         JSON[map[string]string]   `db:"group_name"` | ||||
| 	Homepage      JSON[map[string]string]   `db:"homepage"` | ||||
| 	Maintainer    JSON[map[string]string]   `db:"maintainer"` | ||||
| 	Architectures JSON[[]string]            `sh:"architectures" db:"architectures"` | ||||
| @@ -59,7 +61,7 @@ type version struct { | ||||
| } | ||||
|  | ||||
| type Config interface { | ||||
| 	GetPaths(ctx context.Context) *config.Paths | ||||
| 	GetPaths() *config.Paths | ||||
| } | ||||
|  | ||||
| type Database struct { | ||||
| @@ -82,7 +84,7 @@ func (d *Database) Init(ctx context.Context) error { | ||||
| } | ||||
|  | ||||
| func (d *Database) Connect(ctx context.Context) error { | ||||
| 	dsn := d.config.GetPaths(ctx).DBPath | ||||
| 	dsn := d.config.GetPaths().DBPath | ||||
| 	db, err := sqlx.Open("sqlite", dsn) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -106,7 +108,9 @@ func (d *Database) initDB(ctx context.Context) error { | ||||
| 			version       TEXT NOT NULL, | ||||
| 			release       INT  NOT NULL, | ||||
| 			epoch         INT, | ||||
| 			summary       TEXT CHECK(summary = 'null' OR (JSON_VALID(summary) AND JSON_TYPE(summary) = 'object')), | ||||
| 			description   TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')), | ||||
| 			group_name    TEXT CHECK(group_name = 'null' OR (JSON_VALID(group_name) AND JSON_TYPE(group_name) = 'object')), | ||||
| 			homepage      TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')), | ||||
| 			maintainer    TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')), | ||||
| 			architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')), | ||||
| @@ -204,7 +208,9 @@ func (d *Database) InsertPackage(ctx context.Context, pkg Package) error { | ||||
| 			version, | ||||
| 			release, | ||||
| 			epoch, | ||||
| 			summary, | ||||
| 			description, | ||||
| 			group_name, | ||||
| 			homepage, | ||||
| 			maintainer, | ||||
| 			architectures, | ||||
| @@ -222,7 +228,9 @@ func (d *Database) InsertPackage(ctx context.Context, pkg Package) error { | ||||
| 			:version, | ||||
| 			:release, | ||||
| 			:epoch, | ||||
| 			:summary, | ||||
| 			:description, | ||||
| 			:group_name, | ||||
| 			:homepage, | ||||
| 			:maintainer, | ||||
| 			:architectures, | ||||
|   | ||||
| @@ -33,7 +33,7 @@ import ( | ||||
|  | ||||
| type TestALRConfig struct{} | ||||
|  | ||||
| func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths { | ||||
| func (c *TestALRConfig) GetPaths() *config.Paths { | ||||
| 	return &config.Paths{ | ||||
| 		DBPath: ":memory:", | ||||
| 	} | ||||
|   | ||||
| @@ -38,7 +38,7 @@ import ( | ||||
|  | ||||
| type TestALRConfig struct{} | ||||
|  | ||||
| func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths { | ||||
| func (c *TestALRConfig) GetPaths() *config.Paths { | ||||
| 	return &config.Paths{ | ||||
| 		CacheDir: "/tmp", | ||||
| 	} | ||||
|   | ||||
| @@ -28,7 +28,7 @@ import ( | ||||
| ) | ||||
|  | ||||
| type Config interface { | ||||
| 	GetPaths(ctx context.Context) *config.Paths | ||||
| 	GetPaths() *config.Paths | ||||
| } | ||||
|  | ||||
| type DownloadCache struct { | ||||
| @@ -43,7 +43,7 @@ func New(cfg Config) *DownloadCache { | ||||
|  | ||||
| func (dc *DownloadCache) BasePath(ctx context.Context) string { | ||||
| 	return filepath.Join( | ||||
| 		dc.cfg.GetPaths(ctx).CacheDir, "dl", | ||||
| 		dc.cfg.GetPaths().CacheDir, "dl", | ||||
| 	) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -36,7 +36,7 @@ type TestALRConfig struct { | ||||
| 	CacheDir string | ||||
| } | ||||
|  | ||||
| func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths { | ||||
| func (c *TestALRConfig) GetPaths() *config.Paths { | ||||
| 	return &config.Paths{ | ||||
| 		CacheDir: c.CacheDir, | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										152
									
								
								internal/logger/hclog.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								internal/logger/hclog.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package logger | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"strings" | ||||
|  | ||||
| 	chLog "github.com/charmbracelet/log" | ||||
| 	"github.com/hashicorp/go-hclog" | ||||
| ) | ||||
|  | ||||
| type HCLoggerAdapter struct { | ||||
| 	logger *Logger | ||||
| } | ||||
|  | ||||
| func hclogLevelTochLog(level hclog.Level) chLog.Level { | ||||
| 	switch level { | ||||
| 	case hclog.Debug: | ||||
| 		return chLog.DebugLevel | ||||
| 	case hclog.Info: | ||||
| 		return chLog.InfoLevel | ||||
| 	case hclog.Warn: | ||||
| 		return chLog.WarnLevel | ||||
| 	case hclog.Error: | ||||
| 		return chLog.ErrorLevel | ||||
| 	} | ||||
| 	return chLog.FatalLevel | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{}) { | ||||
| 	filteredArgs := make([]interface{}, 0, len(args)) | ||||
| 	for i := 0; i < len(args); i += 2 { | ||||
| 		if i+1 >= len(args) { | ||||
| 			filteredArgs = append(filteredArgs, args[i]) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		key, ok := args[i].(string) | ||||
| 		if !ok || key != "timestamp" { | ||||
| 			filteredArgs = append(filteredArgs, args[i], args[i+1]) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Start ugly hacks | ||||
| 	// Ignore exit messages | ||||
| 	// - https://github.com/hashicorp/go-plugin/issues/331 | ||||
| 	// - https://github.com/hashicorp/go-plugin/issues/203 | ||||
| 	// - https://github.com/hashicorp/go-plugin/issues/192 | ||||
| 	var chLogLevel chLog.Level | ||||
| 	if msg == "plugin process exited" || | ||||
| 		strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") || | ||||
| 		strings.HasPrefix(msg, "[DEBUG] plugin") { | ||||
| 		chLogLevel = chLog.DebugLevel | ||||
| 	} else { | ||||
| 		chLogLevel = hclogLevelTochLog(level) | ||||
| 	} | ||||
|  | ||||
| 	a.logger.l.Log(chLogLevel, msg, filteredArgs...) | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) Trace(msg string, args ...interface{}) { | ||||
| 	a.Log(hclog.Trace, msg, args...) | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) Debug(msg string, args ...interface{}) { | ||||
| 	a.Log(hclog.Debug, msg, args...) | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) Info(msg string, args ...interface{}) { | ||||
| 	a.Log(hclog.Info, msg, args...) | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) Warn(msg string, args ...interface{}) { | ||||
| 	a.Log(hclog.Warn, msg, args...) | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) Error(msg string, args ...interface{}) { | ||||
| 	a.Log(hclog.Error, msg, args...) | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) IsTrace() bool { | ||||
| 	return a.logger.l.GetLevel() <= chLog.DebugLevel | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) IsDebug() bool { | ||||
| 	return a.logger.l.GetLevel() <= chLog.DebugLevel | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) IsInfo() bool { | ||||
| 	return a.logger.l.GetLevel() <= chLog.InfoLevel | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) IsWarn() bool { | ||||
| 	return a.logger.l.GetLevel() <= chLog.WarnLevel | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) IsError() bool { | ||||
| 	return a.logger.l.GetLevel() <= chLog.ErrorLevel | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) ImpliedArgs() []interface{} { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) With(args ...interface{}) hclog.Logger { | ||||
| 	return a | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) Name() string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) Named(name string) hclog.Logger { | ||||
| 	return a | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) ResetNamed(name string) hclog.Logger { | ||||
| 	return a | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) SetLevel(level hclog.Level) { | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (a *HCLoggerAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func GetHCLoggerAdapter() *HCLoggerAdapter { | ||||
| 	return &HCLoggerAdapter{ | ||||
| 		logger: logger, | ||||
| 	} | ||||
| } | ||||
| @@ -22,75 +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) chLog.Level { | ||||
| 	switch level { | ||||
| 	case slog.LevelDebug: | ||||
| 		return chLog.DebugLevel | ||||
| 	case slog.LevelInfo: | ||||
| 		return chLog.InfoLevel | ||||
| 	case slog.LevelWarn: | ||||
| 		return chLog.WarnLevel | ||||
| 	case slog.LevelError: | ||||
| 		return chLog.ErrorLevel | ||||
| 	} | ||||
| 	return chLog.FatalLevel | ||||
| } | ||||
|  | ||||
| func (l *Logger) SetLevel(level slog.Level) { | ||||
| 	l.l.SetLevel(slogLevelToLog(level)) | ||||
| } | ||||
|  | ||||
| func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool { | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| func SetupDefault() { | ||||
| 	logger := slog.New(New()) | ||||
| 	slog.SetDefault(logger) | ||||
| var logger *Logger | ||||
|  | ||||
| func SetupDefault() *Logger { | ||||
| 	logger = New() | ||||
| 	slogLogger := slog.New(logger) | ||||
| 	slog.SetDefault(slogLogger) | ||||
| 	return logger | ||||
| } | ||||
|  | ||||
| func SetupForGoPlugin() { | ||||
| 	logger.l.SetFormatter(chLog.JSONFormatter) | ||||
| 	chLog.TimestampKey = "@timestamp" | ||||
| 	chLog.MessageKey = "@message" | ||||
| 	chLog.LevelKey = "@level" | ||||
| } | ||||
|  | ||||
| func GetLogger() *Logger { | ||||
| 	return logger | ||||
| } | ||||
|   | ||||
| @@ -157,6 +157,8 @@ type ResolvedPackage struct { | ||||
| 	Version       string   `sh:"version"` | ||||
| 	Release       int      `sh:"release"` | ||||
| 	Epoch         uint     `sh:"epoch"` | ||||
| 	Group         string   `db:"group_name"` | ||||
| 	Summary       string   `db:"summary"` | ||||
| 	Description   string   `db:"description"` | ||||
| 	Homepage      string   `db:"homepage"` | ||||
| 	Maintainer    string   `db:"maintainer"` | ||||
|   | ||||
| @@ -123,15 +123,29 @@ func (d *Decoder) DecodeVars(val any) error { | ||||
| 	} | ||||
|  | ||||
| 	rVal := reflect.ValueOf(val).Elem() | ||||
| 	return d.decodeStruct(rVal) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) decodeStruct(rVal reflect.Value) error { | ||||
| 	for i := 0; i < rVal.NumField(); i++ { | ||||
| 		field := rVal.Field(i) | ||||
| 		fieldType := rVal.Type().Field(i) | ||||
|  | ||||
| 		// Пропускаем неэкспортируемые поля | ||||
| 		if !fieldType.IsExported() { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// Обрабатываем встроенные поля рекурсивно | ||||
| 		if fieldType.Anonymous { | ||||
| 			if field.Kind() == reflect.Struct { | ||||
| 				if err := d.decodeStruct(field); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		name := fieldType.Name | ||||
| 		tag := fieldType.Tag.Get("sh") | ||||
| 		required := false | ||||
| @@ -160,7 +174,6 @@ func (d *Decoder) DecodeVars(val any) error { | ||||
|  | ||||
| 		field.Set(newVal.Elem()) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -9,87 +9,83 @@ msgstr "" | ||||
| "Content-Transfer-Encoding: 8bit\n" | ||||
| "Plural-Forms: nplurals=2; plural=(n != 1);\n" | ||||
|  | ||||
| #: build.go:44 | ||||
| #: build.go:42 | ||||
| msgid "Build a local package" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:50 | ||||
| #: build.go:48 | ||||
| msgid "Path to the build script" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:55 | ||||
| #: build.go:53 | ||||
| msgid "Specify subpackage in script (for multi package script only)" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:60 | ||||
| #: build.go:58 | ||||
| msgid "Name of the package to build and its repo (example: default/go-bin)" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:65 | ||||
| #: build.go:63 | ||||
| msgid "" | ||||
| "Build package from scratch even if there's an already built package available" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:75 | ||||
| msgid "Error initialization database" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:104 | ||||
| msgid "Package not found" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:124 | ||||
| msgid "Error pulling repositories" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:132 | ||||
| msgid "Unable to detect a supported package manager on the system" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:138 | ||||
| msgid "Error parsing os release" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:160 | ||||
| msgid "Error building package" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:167 | ||||
| #: build.go:73 | ||||
| msgid "Error getting working directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:176 | ||||
| #: build.go:118 | ||||
| msgid "Cannot get absolute script path" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:148 | ||||
| msgid "Package not found" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:161 | ||||
| msgid "Nothing to build" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:218 | ||||
| msgid "Error building package" | ||||
| msgstr "" | ||||
|  | ||||
| #: build.go:225 | ||||
| msgid "Error moving the package" | ||||
| msgstr "" | ||||
|  | ||||
| #: fix.go:37 | ||||
| #: build.go:229 | ||||
| msgid "Done" | ||||
| msgstr "" | ||||
|  | ||||
| #: fix.go:38 | ||||
| msgid "Attempt to fix problems with ALR" | ||||
| msgstr "" | ||||
|  | ||||
| #: fix.go:43 | ||||
| msgid "Removing cache directory" | ||||
| #: fix.go:59 | ||||
| msgid "Clearing cache directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: fix.go:47 | ||||
| msgid "Unable to remove cache directory" | ||||
| #: fix.go:64 | ||||
| msgid "Unable to open cache directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: fix.go:51 | ||||
| #: fix.go:70 | ||||
| msgid "Unable to read cache directory contents" | ||||
| msgstr "" | ||||
|  | ||||
| #: fix.go:76 | ||||
| msgid "Unable to remove cache item (%s)" | ||||
| msgstr "" | ||||
|  | ||||
| #: fix.go:80 | ||||
| msgid "Rebuilding cache" | ||||
| msgstr "" | ||||
|  | ||||
| #: fix.go:55 | ||||
| #: fix.go:84 | ||||
| msgid "Unable to create new cache directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: fix.go:69 | ||||
| msgid "Error pulling repos" | ||||
| msgstr "" | ||||
|  | ||||
| #: fix.go:73 | ||||
| msgid "Done" | ||||
| msgstr "" | ||||
|  | ||||
| #: gen.go:34 | ||||
| msgid "Generate a ALR script from a template" | ||||
| msgstr "" | ||||
| @@ -98,78 +94,106 @@ msgstr "" | ||||
| msgid "Generate a ALR script for a pip module" | ||||
| msgstr "" | ||||
|  | ||||
| #: helper.go:41 | ||||
| #: helper.go:42 | ||||
| msgid "List all the available helper commands" | ||||
| msgstr "" | ||||
|  | ||||
| #: helper.go:53 | ||||
| #: helper.go:54 | ||||
| msgid "Run a ALR helper command" | ||||
| msgstr "" | ||||
|  | ||||
| #: helper.go:60 | ||||
| #: helper.go:61 | ||||
| msgid "The directory that the install commands will install to" | ||||
| msgstr "" | ||||
|  | ||||
| #: helper.go:73 | ||||
| #: helper.go:74 helper.go:75 | ||||
| msgid "No such helper command" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:43 | ||||
| msgid "Print information about a package" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:48 | ||||
| msgid "Show all information, not just for the current distro" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:63 | ||||
| msgid "Error getting packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:72 | ||||
| msgid "Error iterating over packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:93 | ||||
| msgid "Command info expected at least 1 argument, got %d" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:107 | ||||
| msgid "Error finding packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:129 | ||||
| #: helper.go:85 | ||||
| msgid "Error parsing os-release file" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:138 | ||||
| #: info.go:42 | ||||
| msgid "Print information about a package" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:47 | ||||
| msgid "Show all information, not just for the current distro" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:68 | ||||
| msgid "Error getting packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:76 | ||||
| msgid "Error iterating over packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:90 | ||||
| msgid "Command info expected at least 1 argument, got %d" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:110 | ||||
| msgid "Error finding packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:124 | ||||
| msgid "Can't detect system language" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:141 | ||||
| msgid "Error resolving overrides" | ||||
| msgstr "" | ||||
|  | ||||
| #: info.go:147 info.go:153 | ||||
| #: info.go:149 info.go:154 | ||||
| msgid "Error encoding script variables" | ||||
| msgstr "" | ||||
|  | ||||
| #: install.go:43 | ||||
| #: install.go:40 | ||||
| msgid "Install a new package" | ||||
| msgstr "" | ||||
|  | ||||
| #: install.go:57 | ||||
| #: install.go:56 | ||||
| msgid "Command install expected at least 1 argument, got %d" | ||||
| msgstr "" | ||||
|  | ||||
| #: install.go:151 | ||||
| #: install.go:118 | ||||
| msgid "Error parsing os release" | ||||
| msgstr "" | ||||
|  | ||||
| #: install.go:163 | ||||
| msgid "Remove an installed package" | ||||
| msgstr "" | ||||
|  | ||||
| #: install.go:156 | ||||
| #: install.go:182 | ||||
| msgid "Error listing installed packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: install.go:223 | ||||
| msgid "Command remove expected at least 1 argument, got %d" | ||||
| msgstr "" | ||||
|  | ||||
| #: install.go:168 | ||||
| #: install.go:238 | ||||
| msgid "Error removing packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/cliutils/app_builder/builder.go:75 | ||||
| msgid "Error loading config" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/cliutils/app_builder/builder.go:96 | ||||
| msgid "Error initialization database" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/cliutils/app_builder/builder.go:135 | ||||
| msgid "Error pulling repositories" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/cliutils/app_builder/builder.go:165 | ||||
| msgid "Unable to detect a supported package manager on the system" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/cliutils/prompt.go:60 | ||||
| msgid "Would you like to view the build script for %s" | ||||
| msgstr "" | ||||
| @@ -250,47 +274,11 @@ msgstr "" | ||||
| msgid "OPTIONS" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/config/config.go:59 | ||||
| msgid "Error opening config file, using defaults" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/config/config.go:72 | ||||
| msgid "Error decoding config file, using defaults" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/config/config.go:84 | ||||
| msgid "Unable to detect user config directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/config/config.go:92 | ||||
| msgid "Unable to create ALR config directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/config/config.go:101 | ||||
| msgid "Unable to create ALR config file" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/config/config.go:107 | ||||
| msgid "Error encoding default configuration" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/config/config.go:116 | ||||
| msgid "Unable to detect cache directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/config/config.go:126 | ||||
| msgid "Unable to create repo cache directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/config/config.go:132 | ||||
| msgid "Unable to create package cache directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/db/db.go:133 | ||||
| #: internal/db/db.go:137 | ||||
| msgid "Database version mismatch; resetting" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/db/db.go:140 | ||||
| #: internal/db/db.go:144 | ||||
| msgid "" | ||||
| "Database version does not exist. Run alr fix if something isn't working." | ||||
| msgstr "" | ||||
| @@ -319,18 +307,26 @@ 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:95 | ||||
| msgid "Error on dropping capabilities" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/utils/cmd.go:123 | ||||
| msgid "You need to be root to perform this action" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/utils/cmd.go:165 | ||||
| msgid "You need to be a %s member to perform this action" | ||||
| msgstr "" | ||||
|  | ||||
| #: list.go:41 | ||||
| msgid "List ALR repo packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: list.go:92 | ||||
| msgid "Error listing installed packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: main.go:45 | ||||
| msgid "Print the current ALR version and exit" | ||||
| msgstr "" | ||||
| @@ -343,106 +339,76 @@ msgstr "" | ||||
| msgid "Enable interactive questions and prompts" | ||||
| msgstr "" | ||||
|  | ||||
| #: main.go:92 | ||||
| msgid "" | ||||
| "Running ALR as root is forbidden as it may cause catastrophic damage to your " | ||||
| "system" | ||||
| msgstr "" | ||||
|  | ||||
| #: main.go:125 | ||||
| #: main.go:145 | ||||
| msgid "Show help" | ||||
| msgstr "" | ||||
|  | ||||
| #: main.go:129 | ||||
| #: main.go:149 | ||||
| msgid "Error while running app" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:156 | ||||
| msgid "Failed to prompt user to view build script" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:160 | ||||
| #: pkg/build/build.go:395 | ||||
| msgid "Building package" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:208 | ||||
| #: pkg/build/build.go:424 | ||||
| msgid "The checksums array must be the same length as sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:235 | ||||
| #: pkg/build/build.go:455 | ||||
| msgid "Downloading sources" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:257 | ||||
| msgid "Building package metadata" | ||||
| #: pkg/build/build.go:549 | ||||
| msgid "Installing dependencies" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:279 | ||||
| msgid "Compressing package" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:438 | ||||
| #: 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:452 | ||||
| #: pkg/build/checker.go:67 | ||||
| msgid "This package is already installed" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:476 | ||||
| msgid "Installing build dependencies" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:517 | ||||
| msgid "Installing dependencies" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:598 | ||||
| msgid "Would you like to remove the build dependencies?" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:661 | ||||
| msgid "Executing prepare()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:671 | ||||
| msgid "Executing build()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:701 pkg/build/build.go:721 | ||||
| msgid "Executing %s()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:780 | ||||
| msgid "Error installing native packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:804 | ||||
| msgid "Error installing package" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:863 | ||||
| msgid "AutoProv is not implemented for this package format, so it's skipped" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:874 | ||||
| msgid "AutoReq is not implemented for this package format, so it's skipped" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/findDeps.go:35 | ||||
| #: pkg/build/find_deps/alt_linux.go:35 | ||||
| msgid "Command not found on the system" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/findDeps.go:82 | ||||
| #: pkg/build/find_deps/alt_linux.go:86 | ||||
| msgid "Provided dependency found" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/findDeps.go:89 | ||||
| #: pkg/build/find_deps/alt_linux.go:93 | ||||
| msgid "Required dependency found" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/find_deps/empty.go:32 | ||||
| msgid "AutoProv is not implemented for this package format, so it's skipped" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/find_deps/empty.go:37 | ||||
| msgid "AutoReq is not implemented for this package format, so it's skipped" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/script_executor.go:237 | ||||
| msgid "Building package metadata" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/script_executor.go:368 | ||||
| msgid "Executing prepare()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/script_executor.go:377 | ||||
| msgid "Executing build()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/script_executor.go:406 pkg/build/script_executor.go:426 | ||||
| msgid "Executing %s()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/repos/pull.go:79 | ||||
| msgid "Pulling repository" | ||||
| msgstr "" | ||||
| @@ -461,79 +427,83 @@ msgid "" | ||||
| "updating ALR if something doesn't work." | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:41 | ||||
| #: repo.go:39 | ||||
| msgid "Add a new repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:48 | ||||
| #: repo.go:46 | ||||
| msgid "Name of the new repo" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:54 | ||||
| #: repo.go:52 | ||||
| msgid "URL of the new repo" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:82 repo.go:147 | ||||
| msgid "Error opening config file" | ||||
| #: repo.go:79 | ||||
| msgid "Repo \"%s\" already exists" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:88 repo.go:153 | ||||
| msgid "Error encoding config" | ||||
| #: repo.go:90 repo.go:167 | ||||
| msgid "Error saving config" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:113 | ||||
| #: repo.go:116 | ||||
| msgid "Remove an existing repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:120 | ||||
| #: repo.go:123 | ||||
| msgid "Name of the repo to be deleted" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:139 | ||||
| msgid "Repo does not exist" | ||||
| #: repo.go:156 | ||||
| msgid "Repo \"%s\" does not exist" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:159 | ||||
| #: repo.go:163 | ||||
| msgid "Error removing repo directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:170 | ||||
| #: repo.go:186 | ||||
| msgid "Error removing packages from database" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:182 | ||||
| #: repo.go:197 | ||||
| msgid "Pull all repositories that have changed" | ||||
| msgstr "" | ||||
|  | ||||
| #: search.go:36 | ||||
| #: search.go:40 | ||||
| msgid "Search packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: search.go:42 | ||||
| #: search.go:51 | ||||
| msgid "Search by name" | ||||
| msgstr "" | ||||
|  | ||||
| #: search.go:47 | ||||
| #: search.go:56 | ||||
| msgid "Search by description" | ||||
| msgstr "" | ||||
|  | ||||
| #: search.go:52 | ||||
| #: search.go:61 | ||||
| msgid "Search by repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: search.go:57 | ||||
| #: search.go:66 | ||||
| msgid "Search by provides" | ||||
| msgstr "" | ||||
|  | ||||
| #: search.go:62 | ||||
| #: search.go:71 | ||||
| msgid "Format output using a Go template" | ||||
| msgstr "" | ||||
|  | ||||
| #: search.go:82 search.go:99 | ||||
| #: search.go:130 | ||||
| msgid "Error while executing search" | ||||
| msgstr "" | ||||
|  | ||||
| #: search.go:138 | ||||
| msgid "Error parsing format template" | ||||
| msgstr "" | ||||
|  | ||||
| #: search.go:107 | ||||
| #: search.go:153 | ||||
| msgid "Error executing template" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -541,10 +511,10 @@ msgstr "" | ||||
| msgid "Upgrade all installed packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: upgrade.go:90 | ||||
| #: upgrade.go:109 upgrade.go:126 | ||||
| msgid "Error checking for updates" | ||||
| msgstr "" | ||||
|  | ||||
| #: upgrade.go:112 | ||||
| #: upgrade.go:129 | ||||
| msgid "There is nothing to do." | ||||
| msgstr "" | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: unnamed project\n" | ||||
| "PO-Revision-Date: 2025-03-09 17:31+0300\n" | ||||
| "PO-Revision-Date: 2025-04-18 07:38+0300\n" | ||||
| "Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n" | ||||
| "Language-Team: Russian\n" | ||||
| "Language: ru\n" | ||||
| @@ -14,89 +14,85 @@ msgstr "" | ||||
| "Content-Transfer-Encoding: 8bit\n" | ||||
| "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" | ||||
| "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | ||||
| "X-Generator: Gtranslator 47.1\n" | ||||
| "X-Generator: Gtranslator 48.0\n" | ||||
|  | ||||
| #: build.go:44 | ||||
| #: build.go:42 | ||||
| msgid "Build a local package" | ||||
| msgstr "Сборка локального пакета" | ||||
|  | ||||
| #: build.go:50 | ||||
| #: build.go:48 | ||||
| msgid "Path to the build script" | ||||
| msgstr "Путь к скрипту сборки" | ||||
|  | ||||
| #: build.go:55 | ||||
| #: build.go:53 | ||||
| msgid "Specify subpackage in script (for multi package script only)" | ||||
| msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)" | ||||
|  | ||||
| #: build.go:60 | ||||
| #: build.go:58 | ||||
| msgid "Name of the package to build and its repo (example: default/go-bin)" | ||||
| msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)" | ||||
|  | ||||
| #: build.go:65 | ||||
| #: build.go:63 | ||||
| msgid "" | ||||
| "Build package from scratch even if there's an already built package available" | ||||
| msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет" | ||||
|  | ||||
| #: build.go:75 | ||||
| msgid "Error initialization database" | ||||
| msgstr "Ошибка инициализации базы данных" | ||||
|  | ||||
| #: build.go:104 | ||||
| msgid "Package not found" | ||||
| msgstr "Пакет не найден" | ||||
|  | ||||
| #: build.go:124 | ||||
| msgid "Error pulling repositories" | ||||
| msgstr "Ошибка при извлечении репозиториев" | ||||
|  | ||||
| #: build.go:132 | ||||
| msgid "Unable to detect a supported package manager on the system" | ||||
| msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" | ||||
|  | ||||
| #: build.go:138 | ||||
| msgid "Error parsing os release" | ||||
| msgstr "Ошибка при разборе файла выпуска операционной системы" | ||||
|  | ||||
| #: build.go:160 | ||||
| msgid "Error building package" | ||||
| msgstr "Ошибка при сборке пакета" | ||||
|  | ||||
| #: build.go:167 | ||||
| #: build.go:73 | ||||
| msgid "Error getting working directory" | ||||
| msgstr "Ошибка при получении рабочего каталога" | ||||
|  | ||||
| #: build.go:176 | ||||
| #: build.go:118 | ||||
| msgid "Cannot get absolute script path" | ||||
| msgstr "Невозможно получить абсолютный путь к скрипту" | ||||
|  | ||||
| #: build.go:148 | ||||
| msgid "Package not found" | ||||
| msgstr "Пакет не найден" | ||||
|  | ||||
| #: build.go:161 | ||||
| msgid "Nothing to build" | ||||
| msgstr "Нечего собирать" | ||||
|  | ||||
| #: build.go:218 | ||||
| msgid "Error building package" | ||||
| msgstr "Ошибка при сборке пакета" | ||||
|  | ||||
| #: build.go:225 | ||||
| msgid "Error moving the package" | ||||
| msgstr "Ошибка при перемещении пакета" | ||||
|  | ||||
| #: fix.go:37 | ||||
| #: build.go:229 | ||||
| msgid "Done" | ||||
| msgstr "Сделано" | ||||
|  | ||||
| #: fix.go:38 | ||||
| msgid "Attempt to fix problems with ALR" | ||||
| msgstr "Попытка устранить проблемы с ALR" | ||||
|  | ||||
| #: fix.go:43 | ||||
| msgid "Removing cache directory" | ||||
| msgstr "Удаление каталога кэша" | ||||
| #: fix.go:59 | ||||
| msgid "Clearing cache directory" | ||||
| msgstr "Очистка каталога кэша" | ||||
|  | ||||
| #: fix.go:47 | ||||
| msgid "Unable to remove cache directory" | ||||
| msgstr "Не удалось удалить каталог кэша" | ||||
| #: fix.go:64 | ||||
| msgid "Unable to open cache directory" | ||||
| msgstr "Невозможно открыть каталог кэша" | ||||
|  | ||||
| #: fix.go:51 | ||||
| #: fix.go:70 | ||||
| msgid "Unable to read cache directory contents" | ||||
| msgstr "Невозможно прочитать содержимое каталога кэша" | ||||
|  | ||||
| #: fix.go:76 | ||||
| msgid "Unable to remove cache item (%s)" | ||||
| msgstr "Невозможно удалить элемент кэша (%s)" | ||||
|  | ||||
| #: fix.go:80 | ||||
| msgid "Rebuilding cache" | ||||
| msgstr "Восстановление кэша" | ||||
|  | ||||
| #: fix.go:55 | ||||
| #: fix.go:84 | ||||
| msgid "Unable to create new cache directory" | ||||
| msgstr "Не удалось создать новый каталог кэша" | ||||
|  | ||||
| #: fix.go:69 | ||||
| msgid "Error pulling repos" | ||||
| msgstr "Ошибка при извлечении репозиториев" | ||||
|  | ||||
| #: fix.go:73 | ||||
| msgid "Done" | ||||
| msgstr "Сделано" | ||||
|  | ||||
| #: gen.go:34 | ||||
| msgid "Generate a ALR script from a template" | ||||
| msgstr "Генерация скрипта ALR из шаблона" | ||||
| @@ -105,78 +101,106 @@ msgstr "Генерация скрипта ALR из шаблона" | ||||
| msgid "Generate a ALR script for a pip module" | ||||
| msgstr "Генерация скрипта ALR для модуля pip" | ||||
|  | ||||
| #: helper.go:41 | ||||
| #: helper.go:42 | ||||
| msgid "List all the available helper commands" | ||||
| msgstr "Список всех доступных вспомогательных команды" | ||||
|  | ||||
| #: helper.go:53 | ||||
| #: helper.go:54 | ||||
| msgid "Run a ALR helper command" | ||||
| msgstr "Запустить вспомогательную команду ALR" | ||||
|  | ||||
| #: helper.go:60 | ||||
| #: helper.go:61 | ||||
| msgid "The directory that the install commands will install to" | ||||
| msgstr "Каталог, в который будут устанавливать команды установки" | ||||
|  | ||||
| #: helper.go:73 | ||||
| #: helper.go:74 helper.go:75 | ||||
| msgid "No such helper command" | ||||
| msgstr "Такой вспомогательной команды нет" | ||||
|  | ||||
| #: info.go:43 | ||||
| msgid "Print information about a package" | ||||
| msgstr "Отобразить информацию о пакете" | ||||
|  | ||||
| #: info.go:48 | ||||
| msgid "Show all information, not just for the current distro" | ||||
| msgstr "Показывать всю информацию, не только для текущего дистрибутива" | ||||
|  | ||||
| #: info.go:63 | ||||
| msgid "Error getting packages" | ||||
| msgstr "Ошибка при получении пакетов" | ||||
|  | ||||
| #: info.go:72 | ||||
| msgid "Error iterating over packages" | ||||
| msgstr "Ошибка при переборе пакетов" | ||||
|  | ||||
| #: info.go:93 | ||||
| msgid "Command info expected at least 1 argument, got %d" | ||||
| msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" | ||||
|  | ||||
| #: info.go:107 | ||||
| msgid "Error finding packages" | ||||
| msgstr "Ошибка при поиске пакетов" | ||||
|  | ||||
| #: info.go:129 | ||||
| #: helper.go:85 | ||||
| msgid "Error parsing os-release file" | ||||
| msgstr "Ошибка при разборе файла выпуска операционной системы" | ||||
|  | ||||
| #: info.go:138 | ||||
| #: info.go:42 | ||||
| msgid "Print information about a package" | ||||
| msgstr "Отобразить информацию о пакете" | ||||
|  | ||||
| #: info.go:47 | ||||
| msgid "Show all information, not just for the current distro" | ||||
| msgstr "Показывать всю информацию, не только для текущего дистрибутива" | ||||
|  | ||||
| #: info.go:68 | ||||
| msgid "Error getting packages" | ||||
| msgstr "Ошибка при получении пакетов" | ||||
|  | ||||
| #: info.go:76 | ||||
| msgid "Error iterating over packages" | ||||
| msgstr "Ошибка при переборе пакетов" | ||||
|  | ||||
| #: info.go:90 | ||||
| msgid "Command info expected at least 1 argument, got %d" | ||||
| msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" | ||||
|  | ||||
| #: info.go:110 | ||||
| msgid "Error finding packages" | ||||
| msgstr "Ошибка при поиске пакетов" | ||||
|  | ||||
| #: info.go:124 | ||||
| msgid "Can't detect system language" | ||||
| msgstr "Ошибка при определении языка системы" | ||||
|  | ||||
| #: info.go:141 | ||||
| msgid "Error resolving overrides" | ||||
| msgstr "Ошибка устранения переорпеделений" | ||||
|  | ||||
| #: info.go:147 info.go:153 | ||||
| #: info.go:149 info.go:154 | ||||
| msgid "Error encoding script variables" | ||||
| msgstr "Ошибка кодирования переменных скрита" | ||||
|  | ||||
| #: install.go:43 | ||||
| #: install.go:40 | ||||
| msgid "Install a new package" | ||||
| msgstr "Установить новый пакет" | ||||
|  | ||||
| #: install.go:57 | ||||
| #: install.go:56 | ||||
| msgid "Command install expected at least 1 argument, got %d" | ||||
| msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" | ||||
|  | ||||
| #: install.go:151 | ||||
| #: install.go:118 | ||||
| msgid "Error parsing os release" | ||||
| msgstr "Ошибка при разборе файла выпуска операционной системы" | ||||
|  | ||||
| #: install.go:163 | ||||
| msgid "Remove an installed package" | ||||
| msgstr "Удалить установленный пакет" | ||||
|  | ||||
| #: install.go:156 | ||||
| #: install.go:182 | ||||
| msgid "Error listing installed packages" | ||||
| msgstr "Ошибка при составлении списка установленных пакетов" | ||||
|  | ||||
| #: install.go:223 | ||||
| msgid "Command remove expected at least 1 argument, got %d" | ||||
| msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d" | ||||
|  | ||||
| #: install.go:168 | ||||
| #: install.go:238 | ||||
| msgid "Error removing packages" | ||||
| msgstr "Ошибка при удалении пакетов" | ||||
|  | ||||
| #: internal/cliutils/app_builder/builder.go:75 | ||||
| msgid "Error loading config" | ||||
| msgstr "Ошибка при загрузке" | ||||
|  | ||||
| #: internal/cliutils/app_builder/builder.go:96 | ||||
| msgid "Error initialization database" | ||||
| msgstr "Ошибка инициализации базы данных" | ||||
|  | ||||
| #: internal/cliutils/app_builder/builder.go:135 | ||||
| msgid "Error pulling repositories" | ||||
| msgstr "Ошибка при извлечении репозиториев" | ||||
|  | ||||
| #: internal/cliutils/app_builder/builder.go:165 | ||||
| msgid "Unable to detect a supported package manager on the system" | ||||
| msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" | ||||
|  | ||||
| #: internal/cliutils/prompt.go:60 | ||||
| msgid "Would you like to view the build script for %s" | ||||
| msgstr "Показать скрипт для пакета %s" | ||||
| @@ -257,51 +281,11 @@ msgstr "КАТЕГОРИЯ" | ||||
| msgid "OPTIONS" | ||||
| msgstr "ПАРАМЕТРЫ" | ||||
|  | ||||
| #: internal/config/config.go:59 | ||||
| msgid "Error opening config file, using defaults" | ||||
| msgstr "" | ||||
| "Ошибка при открытии конфигурационного файла, используются значения по " | ||||
| "умолчанию" | ||||
|  | ||||
| #: internal/config/config.go:72 | ||||
| msgid "Error decoding config file, using defaults" | ||||
| msgstr "" | ||||
| "Ошибка при декодировании конфигурационного файла, используются значения по " | ||||
| "умолчанию" | ||||
|  | ||||
| #: internal/config/config.go:84 | ||||
| msgid "Unable to detect user config directory" | ||||
| msgstr "Не удалось обнаружить каталог конфигурации пользователя" | ||||
|  | ||||
| #: internal/config/config.go:92 | ||||
| msgid "Unable to create ALR config directory" | ||||
| msgstr "Не удалось создать каталог конфигурации ALR" | ||||
|  | ||||
| #: internal/config/config.go:101 | ||||
| msgid "Unable to create ALR config file" | ||||
| msgstr "Не удалось создать конфигурационный файл ALR" | ||||
|  | ||||
| #: internal/config/config.go:107 | ||||
| msgid "Error encoding default configuration" | ||||
| msgstr "Ошибка кодирования конфигурации по умолчанию" | ||||
|  | ||||
| #: internal/config/config.go:116 | ||||
| msgid "Unable to detect cache directory" | ||||
| msgstr "Не удалось обнаружить каталог кэша" | ||||
|  | ||||
| #: internal/config/config.go:126 | ||||
| msgid "Unable to create repo cache directory" | ||||
| msgstr "Не удалось создать каталог кэша репозитория" | ||||
|  | ||||
| #: internal/config/config.go:132 | ||||
| msgid "Unable to create package cache directory" | ||||
| msgstr "Не удалось создать каталог кэша пакетов" | ||||
|  | ||||
| #: internal/db/db.go:133 | ||||
| #: internal/db/db.go:137 | ||||
| msgid "Database version mismatch; resetting" | ||||
| msgstr "Несоответствие версий базы данных; сброс настроек" | ||||
|  | ||||
| #: internal/db/db.go:140 | ||||
| #: internal/db/db.go:144 | ||||
| msgid "" | ||||
| "Database version does not exist. Run alr fix if something isn't working." | ||||
| msgstr "" | ||||
| @@ -331,18 +315,26 @@ 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:95 | ||||
| msgid "Error on dropping capabilities" | ||||
| msgstr "Ошибка при понижении привилегий" | ||||
|  | ||||
| #: internal/utils/cmd.go:123 | ||||
| msgid "You need to be root to perform this action" | ||||
| msgstr "Вы должны быть root чтобы выполнить это" | ||||
|  | ||||
| #: internal/utils/cmd.go:165 | ||||
| msgid "You need to be a %s member to perform this action" | ||||
| msgstr "Вы должны быть членом %s чтобы выполнить это" | ||||
|  | ||||
| #: list.go:41 | ||||
| msgid "List ALR repo packages" | ||||
| msgstr "Список пакетов репозитория ALR" | ||||
|  | ||||
| #: list.go:92 | ||||
| msgid "Error listing installed packages" | ||||
| msgstr "Ошибка при составлении списка установленных пакетов" | ||||
|  | ||||
| #: main.go:45 | ||||
| msgid "Print the current ALR version and exit" | ||||
| msgstr "Показать текущую версию ALR и выйти" | ||||
| @@ -355,47 +347,31 @@ msgstr "Аргументы, которые будут переданы мене | ||||
| msgid "Enable interactive questions and prompts" | ||||
| msgstr "Включение интерактивных вопросов и запросов" | ||||
|  | ||||
| #: main.go:92 | ||||
| msgid "" | ||||
| "Running ALR as root is forbidden as it may cause catastrophic damage to your " | ||||
| "system" | ||||
| msgstr "" | ||||
| "Запуск ALR от имени root запрещён, так как это может привести к " | ||||
| "катастрофическому повреждению вашей системы" | ||||
|  | ||||
| #: main.go:125 | ||||
| #: main.go:145 | ||||
| msgid "Show help" | ||||
| msgstr "Показать справку" | ||||
|  | ||||
| #: main.go:129 | ||||
| #: main.go:149 | ||||
| msgid "Error while running app" | ||||
| msgstr "Ошибка при запуске приложения" | ||||
|  | ||||
| #: pkg/build/build.go:156 | ||||
| msgid "Failed to prompt user to view build script" | ||||
| msgstr "Не удалось предложить пользователю просмотреть скрипт сборки" | ||||
|  | ||||
| #: pkg/build/build.go:160 | ||||
| #: pkg/build/build.go:395 | ||||
| msgid "Building package" | ||||
| msgstr "Сборка пакета" | ||||
|  | ||||
| #: pkg/build/build.go:208 | ||||
| #: pkg/build/build.go:424 | ||||
| msgid "The checksums array must be the same length as sources" | ||||
| msgstr "Массив контрольных сумм должен быть той же длины, что и источники" | ||||
|  | ||||
| #: pkg/build/build.go:235 | ||||
| #: pkg/build/build.go:455 | ||||
| msgid "Downloading sources" | ||||
| msgstr "Скачивание источников" | ||||
|  | ||||
| #: pkg/build/build.go:257 | ||||
| msgid "Building package metadata" | ||||
| msgstr "Сборка метаданных пакета" | ||||
| #: pkg/build/build.go:549 | ||||
| msgid "Installing dependencies" | ||||
| msgstr "Установка зависимостей" | ||||
|  | ||||
| #: pkg/build/build.go:279 | ||||
| msgid "Compressing package" | ||||
| msgstr "Сжатие пакета" | ||||
|  | ||||
| #: pkg/build/build.go:438 | ||||
| #: pkg/build/checker.go:43 | ||||
| msgid "" | ||||
| "Your system's CPU architecture doesn't match this package. Do you want to " | ||||
| "build anyway?" | ||||
| @@ -403,63 +379,47 @@ msgstr "" | ||||
| "Архитектура процессора вашей системы не соответствует этому пакету. Вы все " | ||||
| "равно хотите выполнить сборку?" | ||||
|  | ||||
| #: pkg/build/build.go:452 | ||||
| #: pkg/build/checker.go:67 | ||||
| msgid "This package is already installed" | ||||
| msgstr "Этот пакет уже установлен" | ||||
|  | ||||
| #: pkg/build/build.go:476 | ||||
| msgid "Installing build dependencies" | ||||
| msgstr "Установка зависимостей сборки" | ||||
| #: pkg/build/find_deps/alt_linux.go:35 | ||||
| msgid "Command not found on the system" | ||||
| msgstr "Команда не найдена в системе" | ||||
|  | ||||
| #: pkg/build/build.go:517 | ||||
| msgid "Installing dependencies" | ||||
| msgstr "Установка зависимостей" | ||||
| #: pkg/build/find_deps/alt_linux.go:86 | ||||
| msgid "Provided dependency found" | ||||
| msgstr "Найденная предоставленная зависимость" | ||||
|  | ||||
| #: pkg/build/build.go:598 | ||||
| msgid "Would you like to remove the build dependencies?" | ||||
| msgstr "Хотели бы вы удалить зависимости сборки?" | ||||
| #: pkg/build/find_deps/alt_linux.go:93 | ||||
| msgid "Required dependency found" | ||||
| msgstr "Найдена требуемая зависимость" | ||||
|  | ||||
| #: pkg/build/build.go:661 | ||||
| msgid "Executing prepare()" | ||||
| msgstr "Исполнение prepare()" | ||||
|  | ||||
| #: pkg/build/build.go:671 | ||||
| msgid "Executing build()" | ||||
| msgstr "Исполнение build()" | ||||
|  | ||||
| #: pkg/build/build.go:701 pkg/build/build.go:721 | ||||
| msgid "Executing %s()" | ||||
| msgstr "Исполнение %s()" | ||||
|  | ||||
| #: pkg/build/build.go:780 | ||||
| msgid "Error installing native packages" | ||||
| msgstr "Ошибка при установке нативных пакетов" | ||||
|  | ||||
| #: pkg/build/build.go:804 | ||||
| msgid "Error installing package" | ||||
| msgstr "Ошибка при установке пакета" | ||||
|  | ||||
| #: pkg/build/build.go:863 | ||||
| #: pkg/build/find_deps/empty.go:32 | ||||
| msgid "AutoProv is not implemented for this package format, so it's skipped" | ||||
| msgstr "" | ||||
| "AutoProv не реализовано для этого формата пакета, поэтому будет пропущено" | ||||
|  | ||||
| #: pkg/build/build.go:874 | ||||
| #: pkg/build/find_deps/empty.go:37 | ||||
| msgid "AutoReq is not implemented for this package format, so it's skipped" | ||||
| msgstr "" | ||||
| "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" | ||||
|  | ||||
| #: pkg/build/findDeps.go:35 | ||||
| msgid "Command not found on the system" | ||||
| msgstr "Команда не найдена в системе" | ||||
| #: pkg/build/script_executor.go:237 | ||||
| msgid "Building package metadata" | ||||
| msgstr "Сборка метаданных пакета" | ||||
|  | ||||
| #: pkg/build/findDeps.go:82 | ||||
| msgid "Provided dependency found" | ||||
| msgstr "Найденная предоставленная зависимость" | ||||
| #: pkg/build/script_executor.go:368 | ||||
| msgid "Executing prepare()" | ||||
| msgstr "Выполнение prepare()" | ||||
|  | ||||
| #: pkg/build/findDeps.go:89 | ||||
| msgid "Required dependency found" | ||||
| msgstr "Найдена требуемая зависимость" | ||||
| #: pkg/build/script_executor.go:377 | ||||
| msgid "Executing build()" | ||||
| msgstr "Выполнение build()" | ||||
|  | ||||
| #: pkg/build/script_executor.go:406 pkg/build/script_executor.go:426 | ||||
| msgid "Executing %s()" | ||||
| msgstr "Выполнение %s()" | ||||
|  | ||||
| #: pkg/repos/pull.go:79 | ||||
| msgid "Pulling repository" | ||||
| @@ -481,79 +441,83 @@ msgstr "" | ||||
| "Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте " | ||||
| "обновить ALR, если что-то не работает." | ||||
|  | ||||
| #: repo.go:41 | ||||
| #: repo.go:39 | ||||
| msgid "Add a new repository" | ||||
| msgstr "Добавить новый репозиторий" | ||||
|  | ||||
| #: repo.go:48 | ||||
| #: repo.go:46 | ||||
| msgid "Name of the new repo" | ||||
| msgstr "Название нового репозитория" | ||||
|  | ||||
| #: repo.go:54 | ||||
| #: repo.go:52 | ||||
| msgid "URL of the new repo" | ||||
| msgstr "URL-адрес нового репозитория" | ||||
|  | ||||
| #: repo.go:82 repo.go:147 | ||||
| msgid "Error opening config file" | ||||
| msgstr "Ошибка при открытии конфигурационного файла" | ||||
| #: repo.go:79 | ||||
| msgid "Repo \"%s\" already exists" | ||||
| msgstr "Репозиторий \"%s\" уже существует" | ||||
|  | ||||
| #: repo.go:88 repo.go:153 | ||||
| msgid "Error encoding config" | ||||
| msgstr "Ошибка при кодировании конфигурации" | ||||
| #: repo.go:90 repo.go:167 | ||||
| msgid "Error saving config" | ||||
| msgstr "Ошибка при сохранении конфигурации" | ||||
|  | ||||
| #: repo.go:113 | ||||
| #: repo.go:116 | ||||
| msgid "Remove an existing repository" | ||||
| msgstr "Удалить существующий репозиторий" | ||||
|  | ||||
| #: repo.go:120 | ||||
| #: repo.go:123 | ||||
| msgid "Name of the repo to be deleted" | ||||
| msgstr "Название репозитория  удалён" | ||||
|  | ||||
| #: repo.go:139 | ||||
| msgid "Repo does not exist" | ||||
| msgstr "Репозитория не существует" | ||||
| #: repo.go:156 | ||||
| msgid "Repo \"%s\" does not exist" | ||||
| msgstr "Репозитория \"%s\" не существует" | ||||
|  | ||||
| #: repo.go:159 | ||||
| #: repo.go:163 | ||||
| msgid "Error removing repo directory" | ||||
| msgstr "Ошибка при удалении каталога репозитория" | ||||
|  | ||||
| #: repo.go:170 | ||||
| #: repo.go:186 | ||||
| msgid "Error removing packages from database" | ||||
| msgstr "Ошибка при удалении пакетов из базы данных" | ||||
|  | ||||
| #: repo.go:182 | ||||
| #: repo.go:197 | ||||
| msgid "Pull all repositories that have changed" | ||||
| msgstr "Скачать все изменённые репозитории" | ||||
|  | ||||
| #: search.go:36 | ||||
| #: search.go:40 | ||||
| msgid "Search packages" | ||||
| msgstr "Поиск пакетов" | ||||
|  | ||||
| #: search.go:42 | ||||
| #: search.go:51 | ||||
| msgid "Search by name" | ||||
| msgstr "Искать по имени" | ||||
|  | ||||
| #: search.go:47 | ||||
| #: search.go:56 | ||||
| msgid "Search by description" | ||||
| msgstr "Искать по описанию" | ||||
|  | ||||
| #: search.go:52 | ||||
| #: search.go:61 | ||||
| msgid "Search by repository" | ||||
| msgstr "Искать по репозиторию" | ||||
|  | ||||
| #: search.go:57 | ||||
| #: search.go:66 | ||||
| msgid "Search by provides" | ||||
| msgstr "Иcкать по provides" | ||||
|  | ||||
| #: search.go:62 | ||||
| #: search.go:71 | ||||
| msgid "Format output using a Go template" | ||||
| msgstr "Формат выходных данных с использованием шаблона Go" | ||||
|  | ||||
| #: search.go:82 search.go:99 | ||||
| #: search.go:130 | ||||
| msgid "Error while executing search" | ||||
| msgstr "Ошибка при выполнении поиска" | ||||
|  | ||||
| #: search.go:138 | ||||
| msgid "Error parsing format template" | ||||
| msgstr "Ошибка при разборе шаблона" | ||||
|  | ||||
| #: search.go:107 | ||||
| #: search.go:153 | ||||
| msgid "Error executing template" | ||||
| msgstr "Ошибка при выполнении шаблона" | ||||
|  | ||||
| @@ -561,19 +525,84 @@ msgstr "Ошибка при выполнении шаблона" | ||||
| msgid "Upgrade all installed packages" | ||||
| msgstr "Обновить все установленные пакеты" | ||||
|  | ||||
| #: upgrade.go:90 | ||||
| #: upgrade.go:109 upgrade.go:126 | ||||
| msgid "Error checking for updates" | ||||
| msgstr "Ошибка при проверке обновлений" | ||||
|  | ||||
| #: upgrade.go:112 | ||||
| #: upgrade.go:129 | ||||
| msgid "There is nothing to do." | ||||
| msgstr "Здесь нечего делать." | ||||
|  | ||||
| #~ msgid "Error parsing system language" | ||||
| #~ msgstr "Ошибка при парсинге языка системы" | ||||
| #~ msgid "Error pulling repos" | ||||
| #~ msgstr "Ошибка при извлечении репозиториев" | ||||
|  | ||||
| #~ msgid "Error opening database" | ||||
| #~ msgstr "Ошибка при открытии базы данных" | ||||
| #, fuzzy | ||||
| #~ msgid "Error getting current executable" | ||||
| #~ msgstr "Ошибка при получении рабочего каталога" | ||||
|  | ||||
| #, fuzzy | ||||
| #~ msgid "Error mounting" | ||||
| #~ msgstr "Ошибка при кодировании конфигурации" | ||||
|  | ||||
| #, fuzzy | ||||
| #~ msgid "Unable to create config directory" | ||||
| #~ msgstr "Не удалось создать каталог конфигурации ALR" | ||||
|  | ||||
| #~ msgid "Unable to create repo cache directory" | ||||
| #~ msgstr "Не удалось создать каталог кэша репозитория" | ||||
|  | ||||
| #~ msgid "Unable to create package cache directory" | ||||
| #~ msgstr "Не удалось создать каталог кэша пакетов" | ||||
|  | ||||
| #~ msgid "" | ||||
| #~ "Running ALR as root is forbidden as it may cause catastrophic damage to " | ||||
| #~ "your system" | ||||
| #~ msgstr "" | ||||
| #~ "Запуск ALR от имени root запрещён, так как это может привести к " | ||||
| #~ "катастрофическому повреждению вашей системы" | ||||
|  | ||||
| #~ msgid "Failed to prompt user to view build script" | ||||
| #~ msgstr "Не удалось предложить пользователю просмотреть скрипт сборки" | ||||
|  | ||||
| #~ msgid "Compressing package" | ||||
| #~ msgstr "Сжатие пакета" | ||||
|  | ||||
| #~ msgid "Installing build dependencies" | ||||
| #~ msgstr "Установка зависимостей сборки" | ||||
|  | ||||
| #~ msgid "Would you like to remove the build dependencies?" | ||||
| #~ msgstr "Хотели бы вы удалить зависимости сборки?" | ||||
|  | ||||
| #~ msgid "Error installing native packages" | ||||
| #~ msgstr "Ошибка при установке нативных пакетов" | ||||
|  | ||||
| #~ msgid "Error installing package" | ||||
| #~ msgstr "Ошибка при установке пакета" | ||||
|  | ||||
| #~ msgid "Error opening config file, using defaults" | ||||
| #~ msgstr "" | ||||
| #~ "Ошибка при открытии конфигурационного файла, используются значения по " | ||||
| #~ "умолчанию" | ||||
|  | ||||
| #~ msgid "Error decoding config file, using defaults" | ||||
| #~ msgstr "" | ||||
| #~ "Ошибка при декодировании конфигурационного файла, используются значения " | ||||
| #~ "по умолчанию" | ||||
|  | ||||
| #~ msgid "Unable to detect user config directory" | ||||
| #~ msgstr "Не удалось обнаружить каталог конфигурации пользователя" | ||||
|  | ||||
| #~ msgid "Unable to create ALR config file" | ||||
| #~ msgstr "Не удалось создать конфигурационный файл ALR" | ||||
|  | ||||
| #~ msgid "Error encoding default configuration" | ||||
| #~ msgstr "Ошибка кодирования конфигурации по умолчанию" | ||||
|  | ||||
| #~ msgid "Unable to detect cache directory" | ||||
| #~ msgstr "Не удалось обнаружить каталог кэша" | ||||
|  | ||||
| #~ msgid "Error opening config file" | ||||
| #~ msgstr "Ошибка при открытии конфигурационного файла" | ||||
|  | ||||
| #~ msgid "Executing version()" | ||||
| #~ msgstr "Исполнение версия()" | ||||
|   | ||||
| @@ -19,91 +19,52 @@ | ||||
|  | ||||
| package types | ||||
|  | ||||
| import "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||
|  | ||||
| type BuildOpts struct { | ||||
| 	Script      string | ||||
| 	Repository  string | ||||
| 	Packages    []string | ||||
| 	Manager     manager.Manager | ||||
| 	Clean       bool | ||||
| 	Interactive bool | ||||
| } | ||||
|  | ||||
| type BuildVarsPre struct { | ||||
| 	Version       string   `sh:"version,required"` | ||||
| 	Release       int      `sh:"release,required"` | ||||
| 	Epoch         uint     `sh:"epoch"` | ||||
| 	Description   string   `sh:"desc"` | ||||
| 	Homepage      string   `sh:"homepage"` | ||||
| 	Maintainer    string   `sh:"maintainer"` | ||||
| 	Architectures []string `sh:"architectures"` | ||||
| 	Licenses      []string `sh:"license"` | ||||
| 	Provides      []string `sh:"provides"` | ||||
| 	Conflicts     []string `sh:"conflicts"` | ||||
| 	Depends       []string `sh:"deps"` | ||||
| 	BuildDepends  []string `sh:"build_deps"` | ||||
| 	OptDepends    []string `sh:"opt_deps"` | ||||
| 	Replaces      []string `sh:"replaces"` | ||||
| 	Sources       []string `sh:"sources"` | ||||
| 	Checksums     []string `sh:"checksums"` | ||||
| 	Backup        []string `sh:"backup"` | ||||
| 	Scripts       Scripts  `sh:"scripts"` | ||||
| 	AutoReq       []string `sh:"auto_req"` | ||||
| 	AutoProv      []string `sh:"auto_prov"` | ||||
| 	Version          string   `sh:"version,required"` | ||||
| 	Release          int      `sh:"release,required"` | ||||
| 	Epoch            uint     `sh:"epoch"` | ||||
| 	Summary          string   `sh:"summary"` | ||||
| 	Description      string   `sh:"desc"` | ||||
| 	Group            string   `sh:"group"` | ||||
| 	Homepage         string   `sh:"homepage"` | ||||
| 	Maintainer       string   `sh:"maintainer"` | ||||
| 	Architectures    []string `sh:"architectures"` | ||||
| 	Licenses         []string `sh:"license"` | ||||
| 	Provides         []string `sh:"provides"` | ||||
| 	Conflicts        []string `sh:"conflicts"` | ||||
| 	Depends          []string `sh:"deps"` | ||||
| 	BuildDepends     []string `sh:"build_deps"` | ||||
| 	OptDepends       []string `sh:"opt_deps"` | ||||
| 	Replaces         []string `sh:"replaces"` | ||||
| 	Sources          []string `sh:"sources"` | ||||
| 	Checksums        []string `sh:"checksums"` | ||||
| 	Backup           []string `sh:"backup"` | ||||
| 	Scripts          Scripts  `sh:"scripts"` | ||||
| 	AutoReq          []string `sh:"auto_req"` | ||||
| 	AutoProv         []string `sh:"auto_prov"` | ||||
| 	AutoReqSkipList  []string `sh:"auto_req_skiplist"` | ||||
| 	AutoProvSkipList []string `sh:"auto_prov_skiplist"` | ||||
| } | ||||
|  | ||||
| func (bv *BuildVarsPre) ToBuildVars() BuildVars { | ||||
| 	return BuildVars{ | ||||
| 		Name:          "", | ||||
| 		Version:       bv.Version, | ||||
| 		Release:       bv.Release, | ||||
| 		Epoch:         bv.Epoch, | ||||
| 		Description:   bv.Description, | ||||
| 		Homepage:      bv.Homepage, | ||||
| 		Maintainer:    bv.Maintainer, | ||||
| 		Architectures: bv.Architectures, | ||||
| 		Licenses:      bv.Licenses, | ||||
| 		Provides:      bv.Provides, | ||||
| 		Conflicts:     bv.Conflicts, | ||||
| 		Depends:       bv.Depends, | ||||
| 		BuildDepends:  bv.BuildDepends, | ||||
| 		OptDepends:    bv.OptDepends, | ||||
| 		Replaces:      bv.Replaces, | ||||
| 		Sources:       bv.Sources, | ||||
| 		Checksums:     bv.Checksums, | ||||
| 		Backup:        bv.Backup, | ||||
| 		Scripts:       bv.Scripts, | ||||
| 		AutoReq:       bv.AutoReq, | ||||
| 		AutoProv:      bv.AutoProv, | ||||
| 		Name:         "", | ||||
| 		Base:         "", | ||||
| 		BuildVarsPre: *bv, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // BuildVars represents the script variables required | ||||
| // to build a package | ||||
| type BuildVars struct { | ||||
| 	Name          string   `sh:"name,required"` | ||||
| 	Version       string   `sh:"version,required"` | ||||
| 	Release       int      `sh:"release,required"` | ||||
| 	Epoch         uint     `sh:"epoch"` | ||||
| 	Description   string   `sh:"desc"` | ||||
| 	Homepage      string   `sh:"homepage"` | ||||
| 	Maintainer    string   `sh:"maintainer"` | ||||
| 	Architectures []string `sh:"architectures"` | ||||
| 	Licenses      []string `sh:"license"` | ||||
| 	Provides      []string `sh:"provides"` | ||||
| 	Conflicts     []string `sh:"conflicts"` | ||||
| 	Depends       []string `sh:"deps"` | ||||
| 	BuildDepends  []string `sh:"build_deps"` | ||||
| 	OptDepends    []string `sh:"opt_deps"` | ||||
| 	Replaces      []string `sh:"replaces"` | ||||
| 	Sources       []string `sh:"sources"` | ||||
| 	Checksums     []string `sh:"checksums"` | ||||
| 	Backup        []string `sh:"backup"` | ||||
| 	Scripts       Scripts  `sh:"scripts"` | ||||
| 	AutoReq       []string `sh:"auto_req"` | ||||
| 	AutoProv      []string `sh:"auto_prov"` | ||||
| 	Base          string | ||||
| 	Name string `sh:"name,required"` | ||||
| 	Base string | ||||
| 	BuildVarsPre | ||||
| } | ||||
|  | ||||
| type Scripts struct { | ||||
|   | ||||
| @@ -21,12 +21,12 @@ package types | ||||
|  | ||||
| // Config represents the ALR configuration file | ||||
| type Config struct { | ||||
| 	RootCmd          string   `toml:"rootCmd"` | ||||
| 	PagerStyle       string   `toml:"pagerStyle"` | ||||
| 	RootCmd          string   `toml:"rootCmd" env:"ALR_ROOT_CMD"` | ||||
| 	PagerStyle       string   `toml:"pagerStyle" env:"ALR_PAGER_STYLE"` | ||||
| 	IgnorePkgUpdates []string `toml:"ignorePkgUpdates"` | ||||
| 	Repos            []Repo   `toml:"repo"` | ||||
| 	Unsafe           Unsafe   `toml:"unsafe"` | ||||
| 	AutoPull         bool     `toml:"autoPull"` | ||||
| 	AutoPull         bool     `toml:"autoPull" env:"ALR_AUTOPULL"` | ||||
| 	LogLevel         string   `toml:"logLevel" env:"ALR_LOG_LEVEL"` | ||||
| } | ||||
|  | ||||
| // Repo represents a ALR repo within a configuration file | ||||
| @@ -34,7 +34,3 @@ type Repo struct { | ||||
| 	Name string `toml:"name"` | ||||
| 	URL  string `toml:"url"` | ||||
| } | ||||
|  | ||||
| type Unsafe struct { | ||||
| 	AllowRunAsRoot bool `toml:"allowRunAsRoot"` | ||||
| } | ||||
|   | ||||
							
								
								
									
										186
									
								
								internal/utils/cmd.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								internal/utils/cmd.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,186 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package utils | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"os" | ||||
| 	"os/user" | ||||
| 	"strconv" | ||||
| 	"syscall" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/urfave/cli/v2" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||
| ) | ||||
|  | ||||
| func GetUidGidAlrUserString() (string, string, error) { | ||||
| 	u, err := user.Lookup("alr") | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
|  | ||||
| 	return u.Uid, u.Gid, nil | ||||
| } | ||||
|  | ||||
| func GetUidGidAlrUser() (int, int, error) { | ||||
| 	strUid, strGid, err := GetUidGidAlrUserString() | ||||
| 	if err != nil { | ||||
| 		return 0, 0, err | ||||
| 	} | ||||
|  | ||||
| 	uid, err := strconv.Atoi(strUid) | ||||
| 	if err != nil { | ||||
| 		return 0, 0, err | ||||
| 	} | ||||
| 	gid, err := strconv.Atoi(strGid) | ||||
| 	if err != nil { | ||||
| 		return 0, 0, err | ||||
| 	} | ||||
|  | ||||
| 	return uid, gid, nil | ||||
| } | ||||
|  | ||||
| func DropCapsToAlrUser() error { | ||||
| 	uid, gid, err := GetUidGidAlrUser() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	err = syscall.Setgid(gid) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	err = syscall.Setuid(uid) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return EnsureIsAlrUser() | ||||
| } | ||||
|  | ||||
| func ExitIfCantDropGidToAlr() cli.ExitCoder { | ||||
| 	_, gid, err := GetUidGidAlrUser() | ||||
| 	if err != nil { | ||||
| 		return cliutils.FormatCliExit("cannot get gid alr", err) | ||||
| 	} | ||||
| 	err = syscall.Setgid(gid) | ||||
| 	if err != nil { | ||||
| 		return cliutils.FormatCliExit("cannot get setgid alr", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ExitIfCantDropCapsToAlrUser attempts to drop capabilities to the already | ||||
| // running user. Returns a cli.ExitCoder with an error if the operation fails. | ||||
| // See also [ExitIfCantDropCapsToAlrUserNoPrivs] for a version that also applies | ||||
| // no-new-privs. | ||||
| func ExitIfCantDropCapsToAlrUser() cli.ExitCoder { | ||||
| 	err := DropCapsToAlrUser() | ||||
| 	if err != nil { | ||||
| 		return cliutils.FormatCliExit(gotext.Get("Error on dropping capabilities"), err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func ExitIfCantSetNoNewPrivs() cli.ExitCoder { | ||||
| 	if err := NoNewPrivs(); err != nil { | ||||
| 		return cliutils.FormatCliExit("error on NoNewPrivs", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ExitIfCantDropCapsToAlrUserNoPrivs combines [ExitIfCantDropCapsToAlrUser] with [ExitIfCantSetNoNewPrivs] | ||||
| func ExitIfCantDropCapsToAlrUserNoPrivs() cli.ExitCoder { | ||||
| 	if err := ExitIfCantDropCapsToAlrUser(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := ExitIfCantSetNoNewPrivs(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func ExitIfNotRoot() error { | ||||
| 	if os.Getuid() != 0 { | ||||
| 		return cli.Exit(gotext.Get("You need to be root to perform this action"), 1) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func EnsureIsAlrUser() error { | ||||
| 	uid, gid, err := GetUidGidAlrUser() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	newUid := syscall.Getuid() | ||||
| 	if newUid != uid { | ||||
| 		return errors.New("new uid don't matches requested") | ||||
| 	} | ||||
| 	newGid := syscall.Getgid() | ||||
| 	if newGid != gid { | ||||
| 		return errors.New("new gid don't matches requested") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func EnuseIsPrivilegedGroupMember() error { | ||||
| 	currentUser, err := user.Current() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	group, err := user.LookupGroup(constants.PrivilegedGroup) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	groups, err := currentUser.GroupIds() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, gid := range groups { | ||||
| 		if gid == group.Gid { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	return cliutils.FormatCliExit(gotext.Get("You need to be a %s member to perform this action", constants.PrivilegedGroup), nil) | ||||
| } | ||||
|  | ||||
| func EscalateToRootGid() error { | ||||
| 	return syscall.Setgid(0) | ||||
| } | ||||
|  | ||||
| func EscalateToRootUid() error { | ||||
| 	return syscall.Setuid(0) | ||||
| } | ||||
|  | ||||
| func EscalateToRoot() error { | ||||
| 	err := EscalateToRootUid() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	err = EscalateToRootGid() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										23
									
								
								internal/utils/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								internal/utils/utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package utils | ||||
|  | ||||
| import "golang.org/x/sys/unix" | ||||
|  | ||||
| func NoNewPrivs() error { | ||||
| 	return unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) | ||||
| } | ||||
							
								
								
									
										56
									
								
								list.go
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								list.go
									
									
									
									
									
								
							| @@ -22,17 +22,17 @@ package main | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"golang.org/x/exp/slices" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||
| 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||
| ) | ||||
|  | ||||
| func ListCmd() *cli.Command { | ||||
| @@ -47,23 +47,26 @@ func ListCmd() *cli.Command { | ||||
| 			}, | ||||
| 		}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			err := db.Init(ctx) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			rs := repos.New(cfg, db) | ||||
|  | ||||
| 			if cfg.AutoPull(ctx) { | ||||
| 				err = rs.Pull(ctx, cfg.Repos(ctx)) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error pulling repositories"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 				} | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				// autoPull only | ||||
| 				WithRepos(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			cfg := deps.Cfg | ||||
| 			db := deps.DB | ||||
|  | ||||
| 			where := "true" | ||||
| 			args := []any(nil) | ||||
| @@ -74,8 +77,7 @@ func ListCmd() *cli.Command { | ||||
|  | ||||
| 			result, err := db.GetPkgs(ctx, where, args...) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error getting packages"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||
| 			} | ||||
| 			defer result.Close() | ||||
|  | ||||
| @@ -83,14 +85,13 @@ func ListCmd() *cli.Command { | ||||
| 			if c.Bool("installed") { | ||||
| 				mgr := manager.Detect() | ||||
| 				if mgr == nil { | ||||
| 					slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) | ||||
| 					os.Exit(1) | ||||
| 					return cli.Exit(gotext.Get("Unable to detect a supported package manager on the system"), 1) | ||||
| 				} | ||||
|  | ||||
| 				installed, err := mgr.ListInstalled(&manager.Opts{AsRoot: false}) | ||||
| 				installed, err := mgr.ListInstalled(&manager.Opts{}) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error listing installed packages"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 					return cli.Exit(err, 1) | ||||
| 				} | ||||
|  | ||||
| 				for pkgName, version := range installed { | ||||
| @@ -107,10 +108,10 @@ func ListCmd() *cli.Command { | ||||
| 				var pkg database.Package | ||||
| 				err := result.StructScan(&pkg) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 					return cli.Exit(err, 1) | ||||
| 				} | ||||
|  | ||||
| 				if slices.Contains(cfg.IgnorePkgUpdates(ctx), pkg.Name) { | ||||
| 				if slices.Contains(cfg.IgnorePkgUpdates(), pkg.Name) { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| @@ -127,11 +128,6 @@ func ListCmd() *cli.Command { | ||||
| 				fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version) | ||||
| 			} | ||||
|  | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error iterating over packages"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										56
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								main.go
									
									
									
									
									
								
							| @@ -82,39 +82,59 @@ func GetApp() *cli.App { | ||||
| 			HelperCmd(), | ||||
| 			VersionCmd(), | ||||
| 			SearchCmd(), | ||||
| 			// Internal commands | ||||
| 			InternalBuildCmd(), | ||||
| 			InternalInstallCmd(), | ||||
| 			InternalMountCmd(), | ||||
| 		}, | ||||
| 		Before: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
|  | ||||
| 			cmd := c.Args().First() | ||||
| 			if cmd != "helper" && !cfg.AllowRunAsRoot(ctx) && 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...) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 		EnableBashCompletion: true, | ||||
| 		ExitErrHandler: func(cCtx *cli.Context, err error) { | ||||
| 			cliutils.HandleExitCoder(err) | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	translations.Setup() | ||||
| 	logger.SetupDefault() | ||||
| func setLogLevel(newLevel string) { | ||||
| 	level := slog.LevelInfo | ||||
| 	switch newLevel { | ||||
| 	case "DEBUG": | ||||
| 		level = slog.LevelDebug | ||||
| 	case "INFO": | ||||
| 		level = slog.LevelInfo | ||||
| 	case "WARN": | ||||
| 		level = slog.LevelWarn | ||||
| 	case "ERROR": | ||||
| 		level = slog.LevelError | ||||
| 	} | ||||
| 	logger, ok := slog.Default().Handler().(*logger.Logger) | ||||
| 	if !ok { | ||||
| 		panic("unexpected") | ||||
| 	} | ||||
| 	logger.SetLevel(level) | ||||
| } | ||||
|  | ||||
| 	app := GetApp() | ||||
| 	cfg := config.New() | ||||
| func main() { | ||||
| 	logger.SetupDefault() | ||||
| 	setLogLevel(os.Getenv("ALR_LOG_LEVEL")) | ||||
| 	translations.Setup() | ||||
|  | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	// Set the root command to the one set in the ALR config | ||||
| 	manager.DefaultRootCmd = cfg.RootCmd(ctx) | ||||
| 	app := GetApp() | ||||
| 	cfg := config.New() | ||||
| 	err := cfg.Load() | ||||
| 	if err != nil { | ||||
| 		slog.Error(gotext.Get("Error loading config"), "err", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 	setLogLevel(cfg.LogLevel()) | ||||
|  | ||||
| 	ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) | ||||
| 	defer cancel() | ||||
| @@ -124,7 +144,7 @@ func main() { | ||||
| 	cli.CommandHelpTemplate = cliutils.GetCommandHelpTemplate() | ||||
| 	cli.HelpFlag.(*cli.BoolFlag).Usage = gotext.Get("Show help") | ||||
|  | ||||
| 	err := app.RunContext(ctx, os.Args) | ||||
| 	err = app.RunContext(ctx, os.Args) | ||||
| 	if err != nil { | ||||
| 		slog.Error(gotext.Get("Error while running app"), "err", err) | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										1202
									
								
								pkg/build/build.go
									
									
									
									
									
								
							
							
						
						
									
										1202
									
								
								pkg/build/build.go
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -144,11 +144,11 @@ func (m *TestManager) IsInstalled(pkg string) (bool, error) { | ||||
| 
 | ||||
| type TestConfig struct{} | ||||
| 
 | ||||
| func (c *TestConfig) PagerStyle(ctx context.Context) string { | ||||
| func (c *TestConfig) PagerStyle() string { | ||||
| 	return "native" | ||||
| } | ||||
| 
 | ||||
| func (c *TestConfig) GetPaths(ctx context.Context) *config.Paths { | ||||
| func (c *TestConfig) GetPaths() *config.Paths { | ||||
| 	return &config.Paths{ | ||||
| 		CacheDir: "/tmp", | ||||
| 	} | ||||
| @@ -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) | ||||
							
								
								
									
										69
									
								
								pkg/build/cache.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								pkg/build/cache.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"github.com/goreleaser/nfpm/v2" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| ) | ||||
|  | ||||
| type Cache struct { | ||||
| 	cfg Config | ||||
| } | ||||
|  | ||||
| func (c *Cache) CheckForBuiltPackage( | ||||
| 	ctx context.Context, | ||||
| 	input *BuildInput, | ||||
| 	vars *types.BuildVars, | ||||
| ) (string, bool, error) { | ||||
| 	filename, err := pkgFileName(input, vars) | ||||
| 	if err != nil { | ||||
| 		return "", false, err | ||||
| 	} | ||||
|  | ||||
| 	pkgPath := filepath.Join(getBaseDir(c.cfg, vars.Name), filename) | ||||
|  | ||||
| 	_, err = os.Stat(pkgPath) | ||||
| 	if err != nil { | ||||
| 		return "", false, nil | ||||
| 	} | ||||
|  | ||||
| 	return pkgPath, true, nil | ||||
| } | ||||
|  | ||||
| func pkgFileName( | ||||
| 	input interface { | ||||
| 		OsInfoProvider | ||||
| 		PkgFormatProvider | ||||
| 		RepositoryProvider | ||||
| 	}, | ||||
| 	vars *types.BuildVars, | ||||
| ) (string, error) { | ||||
| 	pkgInfo := getBasePkgInfo(vars, input) | ||||
|  | ||||
| 	packager, err := nfpm.Get(input.PkgFormat()) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	return packager.ConventionalFileName(pkgInfo), nil | ||||
| } | ||||
							
								
								
									
										74
									
								
								pkg/build/checker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								pkg/build/checker.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"log/slog" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||
| ) | ||||
|  | ||||
| type Checker struct { | ||||
| 	mgr manager.Manager | ||||
| } | ||||
|  | ||||
| func (c *Checker) PerformChecks( | ||||
| 	ctx context.Context, | ||||
| 	input *BuildInput, | ||||
| 	vars *types.BuildVars, | ||||
| ) (bool, error) { | ||||
| 	if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры | ||||
| 		cont, err := cliutils.YesNoPrompt( | ||||
| 			ctx, | ||||
| 			gotext.Get("Your system's CPU architecture doesn't match this package. Do you want to build anyway?"), | ||||
| 			input.opts.Interactive, | ||||
| 			true, | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
|  | ||||
| 		if !cont { | ||||
| 			return false, nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	installed, err := c.mgr.ListInstalled(nil) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
|  | ||||
| 	filename, err := pkgFileName(input, vars) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
|  | ||||
| 	if instVer, ok := installed[filename]; ok { // Если пакет уже установлен, выводим предупреждение | ||||
| 		slog.Warn(gotext.Get("This package is already installed"), | ||||
| 			"name", vars.Name, | ||||
| 			"version", instVer, | ||||
| 		) | ||||
| 	} | ||||
|  | ||||
| 	return true, nil | ||||
| } | ||||
							
								
								
									
										71
									
								
								pkg/build/dirs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								pkg/build/dirs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| ) | ||||
|  | ||||
| type BaseDirProvider interface { | ||||
| 	BaseDir() string | ||||
| } | ||||
|  | ||||
| type SrcDirProvider interface { | ||||
| 	SrcDir() string | ||||
| } | ||||
|  | ||||
| type PkgDirProvider interface { | ||||
| 	PkgDir() string | ||||
| } | ||||
|  | ||||
| type ScriptDirProvider interface { | ||||
| 	ScriptDir() string | ||||
| } | ||||
|  | ||||
| func getDirs( | ||||
| 	cfg Config, | ||||
| 	scriptPath string, | ||||
| 	basePkg string, | ||||
| ) (types.Directories, error) { | ||||
| 	pkgsDir := cfg.GetPaths().PkgsDir | ||||
|  | ||||
| 	scriptPath, err := filepath.Abs(scriptPath) | ||||
| 	if err != nil { | ||||
| 		return types.Directories{}, err | ||||
| 	} | ||||
| 	baseDir := filepath.Join(pkgsDir, basePkg) | ||||
| 	return types.Directories{ | ||||
| 		BaseDir:   getBaseDir(cfg, basePkg), | ||||
| 		SrcDir:    getSrcDir(cfg, basePkg), | ||||
| 		PkgDir:    filepath.Join(baseDir, "pkg"), | ||||
| 		ScriptDir: getScriptDir(scriptPath), | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func getBaseDir(cfg Config, basePkg string) string { | ||||
| 	return filepath.Join(cfg.GetPaths().PkgsDir, basePkg) | ||||
| } | ||||
|  | ||||
| func getSrcDir(cfg Config, basePkg string) string { | ||||
| 	return filepath.Join(getBaseDir(cfg, basePkg), "src") | ||||
| } | ||||
|  | ||||
| func getScriptDir(scriptPath string) string { | ||||
| 	return filepath.Dir(scriptPath) | ||||
| } | ||||
| @@ -14,7 +14,7 @@ | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| package build | ||||
| package finddeps | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| @@ -30,7 +30,7 @@ import ( | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| ) | ||||
| 
 | ||||
| func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, updateFunc func(string)) error { | ||||
| func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, envs []string, updateFunc func(string)) error { | ||||
| 	if _, err := exec.LookPath(command); err != nil { | ||||
| 		slog.Info(gotext.Get("Command not found on the system"), "command", command) | ||||
| 		return nil | ||||
| @@ -49,8 +49,8 @@ func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Dir | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	cmd := exec.Command(command) | ||||
| 	cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n")) | ||||
| 	cmd := exec.CommandContext(ctx, command) | ||||
| 	cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n") + "\n") | ||||
| 	cmd.Env = append(cmd.Env, | ||||
| 		"RPM_BUILD_ROOT="+dirs.PkgDir, | ||||
| 		"RPM_FINDPROV_METHOD=", | ||||
| @@ -58,6 +58,7 @@ func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Dir | ||||
| 		"RPM_DATADIR=", | ||||
| 		"RPM_SUBPACKAGE_NAME=", | ||||
| 	) | ||||
| 	cmd.Env = append(cmd.Env, envs...) | ||||
| 	var out bytes.Buffer | ||||
| 	var stderr bytes.Buffer | ||||
| 	cmd.Stdout = &out | ||||
| @@ -66,6 +67,7 @@ func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Dir | ||||
| 		slog.Error(stderr.String()) | ||||
| 		return err | ||||
| 	} | ||||
| 	slog.Debug(stderr.String()) | ||||
| 
 | ||||
| 	dependencies := strings.Split(strings.TrimSpace(out.String()), "\n") | ||||
| 	for _, dep := range dependencies { | ||||
| @@ -77,15 +79,17 @@ func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Dir | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func rpmFindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error { | ||||
| 	return rpmFindDependencies(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-provides", func(dep string) { | ||||
| type ALTLinuxFindProvReq struct{} | ||||
| 
 | ||||
| func (o *ALTLinuxFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||
| 	return rpmFindDependenciesALTLinux(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-provides", []string{"RPM_FINDPROV_SKIPLIST=" + strings.Join(skiplist, "\n")}, func(dep string) { | ||||
| 		slog.Info(gotext.Get("Provided dependency found"), "dep", dep) | ||||
| 		pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func rpmFindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error { | ||||
| 	return rpmFindDependencies(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", func(dep string) { | ||||
| func (o *ALTLinuxFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||
| 	return rpmFindDependenciesALTLinux(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", []string{"RPM_FINDREQ_SKIPLIST=" + strings.Join(skiplist, "\n")}, func(dep string) { | ||||
| 		slog.Info(gotext.Get("Required dependency found"), "dep", dep) | ||||
| 		pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep) | ||||
| 	}) | ||||
							
								
								
									
										39
									
								
								pkg/build/find_deps/empty.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								pkg/build/find_deps/empty.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package finddeps | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"log/slog" | ||||
|  | ||||
| 	"github.com/goreleaser/nfpm/v2" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| ) | ||||
|  | ||||
| type EmptyFindProvReq struct{} | ||||
|  | ||||
| func (o *EmptyFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||
| 	slog.Info(gotext.Get("AutoProv is not implemented for this package format, so it's skipped")) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (o *EmptyFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||
| 	slog.Info(gotext.Get("AutoReq is not implemented for this package format, so it's skipped")) | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										118
									
								
								pkg/build/find_deps/fedora.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								pkg/build/find_deps/fedora.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package finddeps | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os/exec" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/goreleaser/nfpm/v2" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| ) | ||||
|  | ||||
| type FedoraFindProvReq struct{} | ||||
|  | ||||
| func rpmFindDependenciesFedora(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, args []string, updateFunc func(string)) error { | ||||
| 	if _, err := exec.LookPath(command); err != nil { | ||||
| 		slog.Info(gotext.Get("Command not found on the system"), "command", command) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	var paths []string | ||||
| 	for _, content := range pkgInfo.Contents { | ||||
| 		if content.Type != "dir" { | ||||
| 			paths = append(paths, | ||||
| 				path.Join(dirs.PkgDir, content.Destination), | ||||
| 			) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(paths) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	cmd := exec.CommandContext(ctx, command, args...) | ||||
| 	cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n") + "\n") | ||||
| 	cmd.Env = append(cmd.Env, | ||||
| 		"RPM_BUILD_ROOT="+dirs.PkgDir, | ||||
| 	) | ||||
| 	var out bytes.Buffer | ||||
| 	var stderr bytes.Buffer | ||||
| 	cmd.Stdout = &out | ||||
| 	cmd.Stderr = &stderr | ||||
| 	if err := cmd.Run(); err != nil { | ||||
| 		slog.Error(stderr.String()) | ||||
| 		return err | ||||
| 	} | ||||
| 	slog.Debug(stderr.String()) | ||||
|  | ||||
| 	dependencies := strings.Split(strings.TrimSpace(out.String()), "\n") | ||||
| 	for _, dep := range dependencies { | ||||
| 		if dep != "" { | ||||
| 			updateFunc(dep) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (o *FedoraFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||
| 	return rpmFindDependenciesFedora( | ||||
| 		ctx, | ||||
| 		pkgInfo, | ||||
| 		dirs, | ||||
| 		"/usr/lib/rpm/rpmdeps", | ||||
| 		[]string{ | ||||
| 			"--define=_use_internal_dependency_generator 1", | ||||
| 			"--provides", | ||||
| 			fmt.Sprintf( | ||||
| 				"--define=__provides_exclude_from %s\"", | ||||
| 				strings.Join(skiplist, "|"), | ||||
| 			), | ||||
| 		}, | ||||
| 		func(dep string) { | ||||
| 			slog.Info(gotext.Get("Provided dependency found"), "dep", dep) | ||||
| 			pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep) | ||||
| 		}) | ||||
| } | ||||
|  | ||||
| func (o *FedoraFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||
| 	return rpmFindDependenciesFedora( | ||||
| 		ctx, | ||||
| 		pkgInfo, | ||||
| 		dirs, | ||||
| 		"/usr/lib/rpm/rpmdeps", | ||||
| 		[]string{ | ||||
| 			"--define=_use_internal_dependency_generator 1", | ||||
| 			"--requires", | ||||
| 			fmt.Sprintf( | ||||
| 				"--define=__requires_exclude_from %s", | ||||
| 				strings.Join(skiplist, "|"), | ||||
| 			), | ||||
| 		}, | ||||
| 		func(dep string) { | ||||
| 			slog.Info(gotext.Get("Required dependency found"), "dep", dep) | ||||
| 			pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep) | ||||
| 		}) | ||||
| } | ||||
							
								
								
									
										58
									
								
								pkg/build/find_deps/find_deps.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								pkg/build/find_deps/find_deps.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package finddeps | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"github.com/goreleaser/nfpm/v2" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||
| ) | ||||
|  | ||||
| type ProvReqFinder interface { | ||||
| 	FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error | ||||
| 	FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error | ||||
| } | ||||
|  | ||||
| type ProvReqService struct { | ||||
| 	finder ProvReqFinder | ||||
| } | ||||
|  | ||||
| func New(info *distro.OSRelease, pkgFormat string) *ProvReqService { | ||||
| 	s := &ProvReqService{ | ||||
| 		finder: &EmptyFindProvReq{}, | ||||
| 	} | ||||
| 	if pkgFormat == "rpm" { | ||||
| 		switch info.ID { | ||||
| 		case "altlinux": | ||||
| 			s.finder = &ALTLinuxFindProvReq{} | ||||
| 		case "fedora": | ||||
| 			s.finder = &FedoraFindProvReq{} | ||||
| 		} | ||||
| 	} | ||||
| 	return s | ||||
| } | ||||
|  | ||||
| func (s *ProvReqService) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||
| 	return s.finder.FindProvides(ctx, pkgInfo, dirs, skiplist) | ||||
| } | ||||
|  | ||||
| func (s *ProvReqService) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||
| 	return s.finder.FindRequires(ctx, pkgInfo, dirs, skiplist) | ||||
| } | ||||
							
								
								
									
										54
									
								
								pkg/build/installer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								pkg/build/installer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||
| ) | ||||
|  | ||||
| func NewInstaller(mgr manager.Manager) *Installer { | ||||
| 	return &Installer{ | ||||
| 		mgr: mgr, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type Installer struct{ mgr manager.Manager } | ||||
|  | ||||
| func (i *Installer) InstallLocal(paths []string, opts *manager.Opts) error { | ||||
| 	return i.mgr.InstallLocal(opts, paths...) | ||||
| } | ||||
|  | ||||
| func (i *Installer) Install(pkgs []string, opts *manager.Opts) error { | ||||
| 	return i.mgr.Install(opts, pkgs...) | ||||
| } | ||||
|  | ||||
| func (i *Installer) RemoveAlreadyInstalled(pkgs []string) ([]string, error) { | ||||
| 	filteredPackages := []string{} | ||||
|  | ||||
| 	for _, dep := range pkgs { | ||||
| 		installed, err := i.mgr.IsInstalled(dep) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if installed { | ||||
| 			continue | ||||
| 		} | ||||
| 		filteredPackages = append(filteredPackages, dep) | ||||
| 	} | ||||
|  | ||||
| 	return filteredPackages, nil | ||||
| } | ||||
							
								
								
									
										52
									
								
								pkg/build/main_build.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								pkg/build/main_build.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||
| ) | ||||
|  | ||||
| func NewMainBuilder( | ||||
| 	cfg Config, | ||||
| 	mgr manager.Manager, | ||||
| 	repos PackageFinder, | ||||
| 	scriptExecutor ScriptExecutor, | ||||
| 	installerExecutor InstallerExecutor, | ||||
| ) (*Builder, error) { | ||||
| 	builder := &Builder{ | ||||
| 		scriptExecutor: scriptExecutor, | ||||
| 		cacheExecutor: &Cache{ | ||||
| 			cfg, | ||||
| 		}, | ||||
| 		scriptResolver: &ScriptResolver{ | ||||
| 			cfg, | ||||
| 		}, | ||||
| 		scriptViewerExecutor: &ScriptViewer{ | ||||
| 			config: cfg, | ||||
| 		}, | ||||
| 		checkerExecutor: &Checker{ | ||||
| 			mgr, | ||||
| 		}, | ||||
| 		installerExecutor: installerExecutor, | ||||
| 		sourceExecutor: &SourceDownloader{ | ||||
| 			cfg, | ||||
| 		}, | ||||
| 		repos: repos, | ||||
| 	} | ||||
|  | ||||
| 	return builder, nil | ||||
| } | ||||
							
								
								
									
										40
									
								
								pkg/build/safe_common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								pkg/build/safe_common.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func setCommonCmdEnv(cmd *exec.Cmd) { | ||||
| 	cmd.Env = []string{ | ||||
| 		"HOME=/var/cache/alr", | ||||
| 		"LOGNAME=alr", | ||||
| 		"USER=alr", | ||||
| 		"PATH=/usr/bin:/bin:/usr/local/bin", | ||||
| 	} | ||||
| 	for _, env := range os.Environ() { | ||||
| 		if strings.HasPrefix(env, "LANG=") || | ||||
| 			strings.HasPrefix(env, "LANGUAGE=") || | ||||
| 			strings.HasPrefix(env, "LC_") || | ||||
| 			strings.HasPrefix(env, "ALR_LOG_LEVEL=") { | ||||
| 			cmd.Env = append(cmd.Env, env) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										150
									
								
								pkg/build/safe_installer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								pkg/build/safe_installer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"net/rpc" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"sync" | ||||
| 	"syscall" | ||||
|  | ||||
| 	"github.com/hashicorp/go-plugin" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||
| ) | ||||
|  | ||||
| type InstallerPlugin struct { | ||||
| 	Impl InstallerExecutor | ||||
| } | ||||
|  | ||||
| type InstallerRPC struct { | ||||
| 	client *rpc.Client | ||||
| } | ||||
|  | ||||
| type InstallerRPCServer struct { | ||||
| 	Impl InstallerExecutor | ||||
| } | ||||
|  | ||||
| type InstallArgs struct { | ||||
| 	PackagesOrPaths []string | ||||
| 	Opts            *manager.Opts | ||||
| } | ||||
|  | ||||
| func (r *InstallerRPC) InstallLocal(paths []string, opts *manager.Opts) error { | ||||
| 	return r.client.Call("Plugin.InstallLocal", &InstallArgs{ | ||||
| 		PackagesOrPaths: paths, | ||||
| 		Opts:            opts, | ||||
| 	}, nil) | ||||
| } | ||||
|  | ||||
| func (s *InstallerRPCServer) InstallLocal(args *InstallArgs, reply *struct{}) error { | ||||
| 	return s.Impl.InstallLocal(args.PackagesOrPaths, args.Opts) | ||||
| } | ||||
|  | ||||
| func (r *InstallerRPC) Install(pkgs []string, opts *manager.Opts) error { | ||||
| 	return r.client.Call("Plugin.Install", &InstallArgs{ | ||||
| 		PackagesOrPaths: pkgs, | ||||
| 		Opts:            opts, | ||||
| 	}, nil) | ||||
| } | ||||
|  | ||||
| func (s *InstallerRPCServer) Install(args *InstallArgs, reply *struct{}) error { | ||||
| 	return s.Impl.Install(args.PackagesOrPaths, args.Opts) | ||||
| } | ||||
|  | ||||
| func (r *InstallerRPC) RemoveAlreadyInstalled(paths []string) ([]string, error) { | ||||
| 	var val []string | ||||
| 	err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, &val) | ||||
| 	return val, err | ||||
| } | ||||
|  | ||||
| func (s *InstallerRPCServer) RemoveAlreadyInstalled(pkgs []string, res *[]string) error { | ||||
| 	vars, err := s.Impl.RemoveAlreadyInstalled(pkgs) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*res = vars | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *InstallerPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { | ||||
| 	return &InstallerRPC{client: c}, nil | ||||
| } | ||||
|  | ||||
| func (p *InstallerPlugin) Server(*plugin.MuxBroker) (interface{}, error) { | ||||
| 	return &InstallerRPCServer{Impl: p.Impl}, nil | ||||
| } | ||||
|  | ||||
| func GetSafeInstaller() (InstallerExecutor, func(), error) { | ||||
| 	var err error | ||||
|  | ||||
| 	executable, err := os.Executable() | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	cmd := exec.Command(executable, "_internal-installer") | ||||
| 	setCommonCmdEnv(cmd) | ||||
|  | ||||
| 	slog.Debug("safe installer setup", "uid", syscall.Getuid(), "gid", syscall.Getgid()) | ||||
|  | ||||
| 	client := plugin.NewClient(&plugin.ClientConfig{ | ||||
| 		HandshakeConfig: HandshakeConfig, | ||||
| 		Plugins:         pluginMap, | ||||
| 		Cmd:             cmd, | ||||
| 		Logger:          logger.GetHCLoggerAdapter(), | ||||
| 		SkipHostEnv:     true, | ||||
| 		UnixSocketConfig: &plugin.UnixSocketConfig{ | ||||
| 			Group: "alr", | ||||
| 		}, | ||||
| 		SyncStderr: os.Stderr, | ||||
| 	}) | ||||
| 	rpcClient, err := client.Client() | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	var cleanupOnce sync.Once | ||||
| 	cleanup := func() { | ||||
| 		cleanupOnce.Do(func() { | ||||
| 			client.Kill() | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			slog.Debug("close installer") | ||||
| 			cleanup() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	raw, err := rpcClient.Dispense("installer") | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	executor, ok := raw.(InstallerExecutor) | ||||
| 	if !ok { | ||||
| 		err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw) | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	return executor, cleanup, nil | ||||
| } | ||||
							
								
								
									
										272
									
								
								pkg/build/safe_script_executor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								pkg/build/safe_script_executor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,272 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"net/rpc" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/hashicorp/go-plugin" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| ) | ||||
|  | ||||
| var HandshakeConfig = plugin.HandshakeConfig{ | ||||
| 	ProtocolVersion:  1, | ||||
| 	MagicCookieKey:   "ALR_PLUGIN", | ||||
| 	MagicCookieValue: "-", | ||||
| } | ||||
|  | ||||
| type ScriptExecutorPlugin struct { | ||||
| 	Impl ScriptExecutor | ||||
| } | ||||
|  | ||||
| type ScriptExecutorRPCServer struct { | ||||
| 	Impl ScriptExecutor | ||||
| } | ||||
|  | ||||
| // ============================= | ||||
| // | ||||
| // ReadScript | ||||
| // | ||||
|  | ||||
| func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) { | ||||
| 	var resp *ScriptFile | ||||
| 	err := s.client.Call("Plugin.ReadScript", scriptPath, &resp) | ||||
| 	return resp, err | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile) error { | ||||
| 	file, err := s.Impl.ReadScript(context.Background(), scriptPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = *file | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ============================= | ||||
| // | ||||
| // ExecuteFirstPass | ||||
| // | ||||
|  | ||||
| type ExecuteFirstPassArgs struct { | ||||
| 	Input *BuildInput | ||||
| 	Sf    *ScriptFile | ||||
| } | ||||
|  | ||||
| type ExecuteFirstPassResp struct { | ||||
| 	BasePkg        string | ||||
| 	VarsOfPackages []*types.BuildVars | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) { | ||||
| 	var resp *ExecuteFirstPassResp | ||||
| 	err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{ | ||||
| 		Input: input, | ||||
| 		Sf:    sf, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	return resp.BasePkg, resp.VarsOfPackages, nil | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) ExecuteFirstPass(args *ExecuteFirstPassArgs, resp *ExecuteFirstPassResp) error { | ||||
| 	basePkg, varsOfPackages, err := s.Impl.ExecuteFirstPass(context.Background(), args.Input, args.Sf) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = ExecuteFirstPassResp{ | ||||
| 		BasePkg:        basePkg, | ||||
| 		VarsOfPackages: varsOfPackages, | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ============================= | ||||
| // | ||||
| // PrepareDirs | ||||
| // | ||||
|  | ||||
| type PrepareDirsArgs struct { | ||||
| 	Input   *BuildInput | ||||
| 	BasePkg string | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPC) PrepareDirs( | ||||
| 	ctx context.Context, | ||||
| 	input *BuildInput, | ||||
| 	basePkg string, | ||||
| ) error { | ||||
| 	err := s.client.Call("Plugin.PrepareDirs", &PrepareDirsArgs{ | ||||
| 		Input:   input, | ||||
| 		BasePkg: basePkg, | ||||
| 	}, nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) PrepareDirs(args *PrepareDirsArgs, reply *struct{}) error { | ||||
| 	err := s.Impl.PrepareDirs( | ||||
| 		context.Background(), | ||||
| 		args.Input, | ||||
| 		args.BasePkg, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // ============================= | ||||
| // | ||||
| // ExecuteSecondPass | ||||
| // | ||||
|  | ||||
| type ExecuteSecondPassArgs struct { | ||||
| 	Input          *BuildInput | ||||
| 	Sf             *ScriptFile | ||||
| 	VarsOfPackages []*types.BuildVars | ||||
| 	RepoDeps       []string | ||||
| 	BuiltNames     []string | ||||
| 	BasePkg        string | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPC) ExecuteSecondPass( | ||||
| 	ctx context.Context, | ||||
| 	input *BuildInput, | ||||
| 	sf *ScriptFile, | ||||
| 	varsOfPackages []*types.BuildVars, | ||||
| 	repoDeps []string, | ||||
| 	builtNames []string, | ||||
| 	basePkg string, | ||||
| ) (*SecondPassResult, error) { | ||||
| 	var resp *SecondPassResult | ||||
| 	err := s.client.Call("Plugin.ExecuteSecondPass", &ExecuteSecondPassArgs{ | ||||
| 		Input:          input, | ||||
| 		Sf:             sf, | ||||
| 		VarsOfPackages: varsOfPackages, | ||||
| 		RepoDeps:       repoDeps, | ||||
| 		BuiltNames:     builtNames, | ||||
| 		BasePkg:        basePkg, | ||||
| 	}, &resp) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return resp, nil | ||||
| } | ||||
|  | ||||
| func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *SecondPassResult) error { | ||||
| 	res, err := s.Impl.ExecuteSecondPass( | ||||
| 		context.Background(), | ||||
| 		args.Input, | ||||
| 		args.Sf, | ||||
| 		args.VarsOfPackages, | ||||
| 		args.RepoDeps, | ||||
| 		args.BuiltNames, | ||||
| 		args.BasePkg, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*resp = *res | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // | ||||
| // ============================ | ||||
| // | ||||
|  | ||||
| func (p *ScriptExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) { | ||||
| 	return &ScriptExecutorRPCServer{Impl: p.Impl}, nil | ||||
| } | ||||
|  | ||||
| func (p *ScriptExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { | ||||
| 	return &ScriptExecutorRPC{client: c}, nil | ||||
| } | ||||
|  | ||||
| type ScriptExecutorRPC struct { | ||||
| 	client *rpc.Client | ||||
| } | ||||
|  | ||||
| var pluginMap = map[string]plugin.Plugin{ | ||||
| 	"script-executor": &ScriptExecutorPlugin{}, | ||||
| 	"installer":       &InstallerPlugin{}, | ||||
| } | ||||
|  | ||||
| func GetSafeScriptExecutor() (ScriptExecutor, func(), error) { | ||||
| 	var err error | ||||
|  | ||||
| 	executable, err := os.Executable() | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	cmd := exec.Command(executable, "_internal-safe-script-executor") | ||||
| 	setCommonCmdEnv(cmd) | ||||
|  | ||||
| 	client := plugin.NewClient(&plugin.ClientConfig{ | ||||
| 		HandshakeConfig: HandshakeConfig, | ||||
| 		Plugins:         pluginMap, | ||||
| 		Cmd:             cmd, | ||||
| 		Logger:          logger.GetHCLoggerAdapter(), | ||||
| 		SkipHostEnv:     true, | ||||
| 		UnixSocketConfig: &plugin.UnixSocketConfig{ | ||||
| 			Group: "alr", | ||||
| 		}, | ||||
| 	}) | ||||
| 	rpcClient, err := client.Client() | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	var cleanupOnce sync.Once | ||||
| 	cleanup := func() { | ||||
| 		cleanupOnce.Do(func() { | ||||
| 			client.Kill() | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			slog.Debug("close script-executor") | ||||
| 			cleanup() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	raw, err := rpcClient.Dispense("script-executor") | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	executor, ok := raw.(ScriptExecutor) | ||||
| 	if !ok { | ||||
| 		err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw) | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	return executor, cleanup, nil | ||||
| } | ||||
							
								
								
									
										447
									
								
								pkg/build/script_executor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										447
									
								
								pkg/build/script_executor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,447 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"slices" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/google/shlex" | ||||
| 	"github.com/goreleaser/nfpm/v2" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"mvdan.cc/sh/v3/expand" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| 	"mvdan.cc/sh/v3/syntax" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| 	finddeps "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build/find_deps" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||
| ) | ||||
|  | ||||
| type LocalScriptExecutor struct { | ||||
| 	cfg Config | ||||
| } | ||||
|  | ||||
| func NewLocalScriptExecutor(cfg Config) *LocalScriptExecutor { | ||||
| 	return &LocalScriptExecutor{ | ||||
| 		cfg, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) { | ||||
| 	fl, err := readScript(scriptPath) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &ScriptFile{ | ||||
| 		Path: scriptPath, | ||||
| 		File: fl, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) { | ||||
| 	varsOfPackages := []*types.BuildVars{} | ||||
|  | ||||
| 	scriptDir := filepath.Dir(sf.Path) | ||||
| 	env := createBuildEnvVars(input.info, types.Directories{ScriptDir: scriptDir}) | ||||
|  | ||||
| 	runner, err := interp.New( | ||||
| 		interp.Env(expand.ListEnviron(env...)),                               // Устанавливаем окружение | ||||
| 		interp.StdIO(os.Stdin, os.Stdout, os.Stderr),                         // Устанавливаем стандартный ввод-вывод | ||||
| 		interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение | ||||
| 		interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)),        // Ограничиваем чтение директорий | ||||
| 		interp.StatHandler(handlers.RestrictedStat(scriptDir)),               // Ограничиваем доступ к статистике файлов | ||||
| 		interp.OpenHandler(handlers.RestrictedOpen(scriptDir)),               // Ограничиваем открытие файлов | ||||
| 		interp.Dir(scriptDir), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
|  | ||||
| 	err = runner.Run(ctx, sf.File) // Запускаем скрипт | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
|  | ||||
| 	dec := decoder.New(input.info, runner) // Создаём новый декодер | ||||
|  | ||||
| 	type packages struct { | ||||
| 		BasePkgName string   `sh:"basepkg_name"` | ||||
| 		Names       []string `sh:"name"` | ||||
| 	} | ||||
|  | ||||
| 	var pkgs packages | ||||
| 	err = dec.DecodeVars(&pkgs) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
|  | ||||
| 	if len(pkgs.Names) == 0 { | ||||
| 		return "", nil, errors.New("package name is missing") | ||||
| 	} | ||||
|  | ||||
| 	var vars types.BuildVars | ||||
|  | ||||
| 	if len(pkgs.Names) == 1 { | ||||
| 		err = dec.DecodeVars(&vars) // Декодируем переменные | ||||
| 		if err != nil { | ||||
| 			return "", nil, err | ||||
| 		} | ||||
| 		varsOfPackages = append(varsOfPackages, &vars) | ||||
|  | ||||
| 		return vars.Name, varsOfPackages, nil | ||||
| 	} | ||||
|  | ||||
| 	if len(input.packages) == 0 { | ||||
| 		return "", nil, errors.New("script has multiple packages but package is not specified") | ||||
| 	} | ||||
|  | ||||
| 	for _, pkgName := range input.packages { | ||||
| 		var preVars types.BuildVarsPre | ||||
| 		funcName := fmt.Sprintf("meta_%s", pkgName) | ||||
| 		meta, ok := dec.GetFuncWithSubshell(funcName) | ||||
| 		if !ok { | ||||
| 			return "", nil, errors.New("func is missing") | ||||
| 		} | ||||
| 		r, err := meta(ctx) | ||||
| 		if err != nil { | ||||
| 			return "", nil, err | ||||
| 		} | ||||
| 		d := decoder.New(&distro.OSRelease{}, r) | ||||
| 		err = d.DecodeVars(&preVars) | ||||
| 		if err != nil { | ||||
| 			return "", nil, err | ||||
| 		} | ||||
| 		vars := preVars.ToBuildVars() | ||||
| 		vars.Name = pkgName | ||||
| 		vars.Base = pkgs.BasePkgName | ||||
|  | ||||
| 		varsOfPackages = append(varsOfPackages, &vars) | ||||
| 	} | ||||
|  | ||||
| 	return pkgs.BasePkgName, varsOfPackages, nil | ||||
| } | ||||
|  | ||||
| type SecondPassResult struct { | ||||
| 	BuiltPaths []string | ||||
| 	BuiltNames []string | ||||
| } | ||||
|  | ||||
| func (e *LocalScriptExecutor) PrepareDirs( | ||||
| 	ctx context.Context, | ||||
| 	input *BuildInput, | ||||
| 	basePkg string, | ||||
| ) error { | ||||
| 	dirs, err := getDirs( | ||||
| 		e.cfg, | ||||
| 		input.script, | ||||
| 		basePkg, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = prepareDirs(dirs) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (e *LocalScriptExecutor) ExecuteSecondPass( | ||||
| 	ctx context.Context, | ||||
| 	input *BuildInput, | ||||
| 	sf *ScriptFile, | ||||
| 	varsOfPackages []*types.BuildVars, | ||||
| 	repoDeps []string, | ||||
| 	builtNames []string, | ||||
| 	basePkg string, | ||||
| ) (*SecondPassResult, error) { | ||||
| 	dirs, err := getDirs(e.cfg, sf.Path, basePkg) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	env := createBuildEnvVars(input.info, dirs) | ||||
|  | ||||
| 	fakeroot := handlers.FakerootExecHandler(2 * time.Second) | ||||
| 	runner, err := interp.New( | ||||
| 		interp.Env(expand.ListEnviron(env...)),       // Устанавливаем окружение | ||||
| 		interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод | ||||
| 		interp.ExecHandlers(func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc { | ||||
| 			return helpers.Helpers.ExecHandler(fakeroot) | ||||
| 		}), // Обрабатываем выполнение через fakeroot | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	err = runner.Run(ctx, sf.File) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	dec := decoder.New(input.info, runner) | ||||
|  | ||||
| 	var builtPaths []string | ||||
|  | ||||
| 	err = e.ExecuteFunctions(ctx, dirs, dec) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	for _, vars := range varsOfPackages { | ||||
| 		packageName := "" | ||||
| 		if vars.Base != "" { | ||||
| 			packageName = vars.Name | ||||
| 		} | ||||
|  | ||||
| 		pkgFormat := input.pkgFormat | ||||
|  | ||||
| 		funcOut, err := e.ExecutePackageFunctions( | ||||
| 			ctx, | ||||
| 			dec, | ||||
| 			dirs, | ||||
| 			packageName, | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		slog.Info(gotext.Get("Building package metadata"), "name", basePkg) | ||||
|  | ||||
| 		pkgInfo, err := buildPkgMetadata( | ||||
| 			ctx, | ||||
| 			input, | ||||
| 			vars, | ||||
| 			dirs, | ||||
| 			append( | ||||
| 				repoDeps, | ||||
| 				builtNames..., | ||||
| 			), | ||||
| 			funcOut.Contents, | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета | ||||
| 		pkgPath := filepath.Join(dirs.BaseDir, pkgName)   // Определяем путь к пакету | ||||
|  | ||||
| 		pkgFile, err := os.Create(pkgPath) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		err = packager.Package(pkgInfo, pkgFile) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		builtPaths = append(builtPaths, pkgPath) | ||||
| 		builtNames = append(builtNames, vars.Name) | ||||
| 	} | ||||
|  | ||||
| 	return &SecondPassResult{ | ||||
| 		BuiltPaths: builtPaths, | ||||
| 		BuiltNames: builtNames, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func buildPkgMetadata( | ||||
| 	ctx context.Context, | ||||
| 	input interface { | ||||
| 		OsInfoProvider | ||||
| 		BuildOptsProvider | ||||
| 		PkgFormatProvider | ||||
| 		RepositoryProvider | ||||
| 	}, | ||||
| 	vars *types.BuildVars, | ||||
| 	dirs types.Directories, | ||||
| 	deps []string, | ||||
| 	preferedContents *[]string, | ||||
| ) (*nfpm.Info, error) { | ||||
| 	pkgInfo := getBasePkgInfo(vars, input) | ||||
| 	pkgInfo.Description = vars.Description | ||||
| 	pkgInfo.Platform = "linux" | ||||
| 	pkgInfo.Homepage = vars.Homepage | ||||
| 	pkgInfo.License = strings.Join(vars.Licenses, ", ") | ||||
| 	pkgInfo.Maintainer = vars.Maintainer | ||||
| 	pkgInfo.Overridables = nfpm.Overridables{ | ||||
| 		Conflicts: append(vars.Conflicts, vars.Name), | ||||
| 		Replaces:  vars.Replaces, | ||||
| 		Provides:  append(vars.Provides, vars.Name), | ||||
| 		Depends:   deps, | ||||
| 	} | ||||
| 	pkgInfo.Section = vars.Group | ||||
|  | ||||
| 	pkgFormat := input.PkgFormat() | ||||
| 	info := input.OSRelease() | ||||
|  | ||||
| 	if pkgFormat == "apk" { | ||||
| 		// Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы | ||||
| 		pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool { | ||||
| 			return s == pkgInfo.Name | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	if pkgFormat == "rpm" { | ||||
| 		pkgInfo.RPM.Group = vars.Group | ||||
|  | ||||
| 		if vars.Summary != "" { | ||||
| 			pkgInfo.RPM.Summary = vars.Summary | ||||
| 		} else { | ||||
| 			lines := strings.SplitN(vars.Description, "\n", 2) | ||||
| 			pkgInfo.RPM.Summary = lines[0] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if vars.Epoch != 0 { | ||||
| 		pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10) | ||||
| 	} | ||||
|  | ||||
| 	setScripts(vars, pkgInfo, dirs.ScriptDir) | ||||
|  | ||||
| 	if slices.Contains(vars.Architectures, "all") { | ||||
| 		pkgInfo.Arch = "all" | ||||
| 	} | ||||
|  | ||||
| 	contents, err := buildContents(vars, dirs, preferedContents) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	pkgInfo.Overridables.Contents = contents | ||||
|  | ||||
| 	if len(vars.AutoProv) == 1 && decoder.IsTruthy(vars.AutoProv[0]) { | ||||
| 		f := finddeps.New(info, pkgFormat) | ||||
| 		err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(vars.AutoReq) == 1 && decoder.IsTruthy(vars.AutoReq[0]) { | ||||
| 		f := finddeps.New(info, pkgFormat) | ||||
| 		err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return pkgInfo, nil | ||||
| } | ||||
|  | ||||
| func (e *LocalScriptExecutor) ExecuteFunctions(ctx context.Context, dirs types.Directories, dec *decoder.Decoder) error { | ||||
| 	prepare, ok := dec.GetFunc("prepare") | ||||
| 	if ok { | ||||
| 		slog.Info(gotext.Get("Executing prepare()")) | ||||
|  | ||||
| 		err := prepare(ctx, interp.Dir(dirs.SrcDir)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	build, ok := dec.GetFunc("build") | ||||
| 	if ok { | ||||
| 		slog.Info(gotext.Get("Executing build()")) | ||||
|  | ||||
| 		err := build(ctx, interp.Dir(dirs.SrcDir)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (e *LocalScriptExecutor) ExecutePackageFunctions( | ||||
| 	ctx context.Context, | ||||
| 	dec *decoder.Decoder, | ||||
| 	dirs types.Directories, | ||||
| 	packageName string, | ||||
| ) (*FunctionsOutput, error) { | ||||
| 	output := &FunctionsOutput{} | ||||
| 	var packageFuncName string | ||||
| 	var filesFuncName string | ||||
|  | ||||
| 	if packageName == "" { | ||||
| 		packageFuncName = "package" | ||||
| 		filesFuncName = "files" | ||||
| 	} else { | ||||
| 		packageFuncName = fmt.Sprintf("package_%s", packageName) | ||||
| 		filesFuncName = fmt.Sprintf("files_%s", packageName) | ||||
| 	} | ||||
| 	packageFn, ok := dec.GetFunc(packageFuncName) | ||||
| 	if ok { | ||||
| 		slog.Info(gotext.Get("Executing %s()", packageFuncName)) | ||||
| 		err := packageFn(ctx, interp.Dir(dirs.SrcDir)) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	files, ok := dec.GetFuncP(filesFuncName, func(ctx context.Context, s *interp.Runner) error { | ||||
| 		// It should be done via interp.RunnerOption, | ||||
| 		// but due to the issues below, it cannot be done. | ||||
| 		// - https://github.com/mvdan/sh/issues/962 | ||||
| 		// - https://github.com/mvdan/sh/issues/1125 | ||||
| 		script, err := syntax.NewParser().Parse(strings.NewReader("cd $pkgdir && shopt -s globstar"), "") | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return s.Run(ctx, script) | ||||
| 	}) | ||||
|  | ||||
| 	if ok { | ||||
| 		slog.Info(gotext.Get("Executing %s()", filesFuncName)) | ||||
|  | ||||
| 		buf := &bytes.Buffer{} | ||||
|  | ||||
| 		err := files( | ||||
| 			ctx, | ||||
| 			interp.Dir(dirs.PkgDir), | ||||
| 			interp.StdIO(os.Stdin, buf, os.Stderr), | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		contents, err := shlex.Split(buf.String()) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		output.Contents = &contents | ||||
| 	} | ||||
|  | ||||
| 	return output, nil | ||||
| } | ||||
							
								
								
									
										53
									
								
								pkg/build/script_resolver.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								pkg/build/script_resolver.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| ) | ||||
|  | ||||
| type ScriptResolver struct { | ||||
| 	cfg Config | ||||
| } | ||||
|  | ||||
| type ScriptInfo struct { | ||||
| 	Script     string | ||||
| 	Repository string | ||||
| } | ||||
|  | ||||
| func (s *ScriptResolver) ResolveScript( | ||||
| 	ctx context.Context, | ||||
| 	pkg *db.Package, | ||||
| ) *ScriptInfo { | ||||
| 	var repository, script string | ||||
|  | ||||
| 	repodir := s.cfg.GetPaths().RepoDir | ||||
| 	repository = pkg.Repository | ||||
| 	if pkg.BasePkgName != "" { | ||||
| 		script = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh") | ||||
| 	} else { | ||||
| 		script = filepath.Join(repodir, repository, pkg.Name, "alr.sh") | ||||
| 	} | ||||
|  | ||||
| 	return &ScriptInfo{ | ||||
| 		Repository: repository, | ||||
| 		Script:     script, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										46
									
								
								pkg/build/script_view.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								pkg/build/script_view.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| ) | ||||
|  | ||||
| type ScriptViewerConfig interface { | ||||
| 	PagerStyle() string | ||||
| } | ||||
|  | ||||
| type ScriptViewer struct { | ||||
| 	config ScriptViewerConfig | ||||
| } | ||||
|  | ||||
| func (s *ScriptViewer) ViewScript( | ||||
| 	ctx context.Context, | ||||
| 	input *BuildInput, | ||||
| 	sf *ScriptFile, | ||||
| 	basePkg string, | ||||
| ) error { | ||||
| 	return cliutils.PromptViewScript( | ||||
| 		ctx, | ||||
| 		sf.Path, | ||||
| 		basePkg, | ||||
| 		s.config.PagerStyle(), | ||||
| 		input.opts.Interactive, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										86
									
								
								pkg/build/source_downloader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								pkg/build/source_downloader.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/hex" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | ||||
| ) | ||||
|  | ||||
| type SourceDownloader struct { | ||||
| 	cfg Config | ||||
| } | ||||
|  | ||||
| func NewSourceDownloader(cfg Config) *SourceDownloader { | ||||
| 	return &SourceDownloader{ | ||||
| 		cfg, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *SourceDownloader) DownloadSources( | ||||
| 	ctx context.Context, | ||||
| 	input *BuildInput, | ||||
| 	basePkg string, | ||||
| 	si SourcesInput, | ||||
| ) error { | ||||
| 	for i, src := range si.Sources { | ||||
|  | ||||
| 		opts := dl.Options{ | ||||
| 			Name:        fmt.Sprintf("[%d]", i), | ||||
| 			URL:         src, | ||||
| 			Destination: getSrcDir(s.cfg, basePkg), | ||||
| 			Progress:    os.Stderr, | ||||
| 			LocalDir:    getScriptDir(input.script), | ||||
| 		} | ||||
|  | ||||
| 		if !strings.EqualFold(si.Checksums[i], "SKIP") { | ||||
| 			// Если контрольная сумма содержит двоеточие, используйте часть до двоеточия | ||||
| 			// как алгоритм, а часть после как фактическую контрольную сумму. | ||||
| 			// В противном случае используйте sha256 по умолчанию с целой строкой как контрольной суммой. | ||||
| 			algo, hashData, ok := strings.Cut(si.Checksums[i], ":") | ||||
| 			if ok { | ||||
| 				checksum, err := hex.DecodeString(hashData) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				opts.Hash = checksum | ||||
| 				opts.HashAlgorithm = algo | ||||
| 			} else { | ||||
| 				checksum, err := hex.DecodeString(si.Checksums[i]) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				opts.Hash = checksum | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		opts.DlCache = dlcache.New(s.cfg) | ||||
|  | ||||
| 		err := dl.Download(ctx, opts) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
| @@ -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<package>[^+]+)\+alr-(?P<repo>.+)$`) | ||||
|  | ||||
| func getBasePkgInfo(vars *types.BuildVars, info *distro.OSRelease, opts *types.BuildOpts) *nfpm.Info { | ||||
| func getBasePkgInfo(vars *types.BuildVars, input interface { | ||||
| 	RepositoryProvider | ||||
| 	OsInfoProvider | ||||
| }, | ||||
| ) *nfpm.Info { | ||||
| 	return &nfpm.Info{ | ||||
| 		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 { | ||||
|   | ||||
| @@ -82,6 +82,7 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) { | ||||
| 		interp.ReadDirHandler2(handlers.NopReadDir), | ||||
| 		interp.StatHandler(handlers.NopStat), | ||||
| 		interp.Env(expand.ListEnviron()), | ||||
| 		interp.Dir("/"), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
|   | ||||
| @@ -41,10 +41,10 @@ checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}') | ||||
|  | ||||
| build() { | ||||
| 	cd "$srcdir/{{.Info.Name}}-${version}" | ||||
|   python3 -m build | ||||
|   python -m build --wheel --no-isolation | ||||
| } | ||||
|  | ||||
| package() { | ||||
| 	cd "$srcdir/{{.Info.Name}}-${version}" | ||||
| 	pip install --root="${pkgdir}/" . --no-deps --disable-pip-version-check | ||||
| 	pip install --root="${pkgdir}/" . --no-deps --ignore-installed --disable-pip-version-check | ||||
| } | ||||
|   | ||||
| @@ -28,7 +28,15 @@ import ( | ||||
|  | ||||
| // APK represents the APK package manager | ||||
| type APK struct { | ||||
| 	rootCmd string | ||||
| 	CommonPackageManager | ||||
| } | ||||
|  | ||||
| func NewAPK() *APK { | ||||
| 	return &APK{ | ||||
| 		CommonPackageManager: CommonPackageManager{ | ||||
| 			noConfirmArg: "-i", | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (*APK) Exists() bool { | ||||
| @@ -44,10 +52,6 @@ func (*APK) Format() string { | ||||
| 	return "apk" | ||||
| } | ||||
|  | ||||
| func (a *APK) SetRootCmd(s string) { | ||||
| 	a.rootCmd = s | ||||
| } | ||||
|  | ||||
| func (a *APK) Sync(opts *Opts) error { | ||||
| 	opts = ensureOpts(opts) | ||||
| 	cmd := a.getCmd(opts, "apk", "update") | ||||
| @@ -163,20 +167,3 @@ func (a *APK) IsInstalled(pkg string) (bool, error) { | ||||
| 	} | ||||
| 	return true, nil | ||||
| } | ||||
|  | ||||
| func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
| 	} | ||||
|  | ||||
| 	if !opts.NoConfirm { | ||||
| 		cmd.Args = append(cmd.Args, "-i") | ||||
| 	} | ||||
|  | ||||
| 	return cmd | ||||
| } | ||||
|   | ||||
| @@ -28,7 +28,15 @@ import ( | ||||
|  | ||||
| // APT represents the APT package manager | ||||
| type APT struct { | ||||
| 	rootCmd string | ||||
| 	CommonPackageManager | ||||
| } | ||||
|  | ||||
| func NewAPT() *APT { | ||||
| 	return &APT{ | ||||
| 		CommonPackageManager: CommonPackageManager{ | ||||
| 			noConfirmArg: "-y", | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (*APT) Exists() bool { | ||||
| @@ -44,10 +52,6 @@ func (*APT) Format() string { | ||||
| 	return "deb" | ||||
| } | ||||
|  | ||||
| func (a *APT) SetRootCmd(s string) { | ||||
| 	a.rootCmd = s | ||||
| } | ||||
|  | ||||
| func (a *APT) Sync(opts *Opts) error { | ||||
| 	opts = ensureOpts(opts) | ||||
| 	cmd := a.getCmd(opts, "apt", "update") | ||||
| @@ -149,20 +153,3 @@ func (a *APT) IsInstalled(pkg string) (bool, error) { | ||||
| 	} | ||||
| 	return true, nil | ||||
| } | ||||
|  | ||||
| func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
| 	} | ||||
|  | ||||
| 	if opts.NoConfirm { | ||||
| 		cmd.Args = append(cmd.Args, "-y") | ||||
| 	} | ||||
|  | ||||
| 	return cmd | ||||
| } | ||||
|   | ||||
| @@ -24,18 +24,16 @@ import ( | ||||
|  | ||||
| // APTRpm represents the APT-RPM package manager | ||||
| type APTRpm struct { | ||||
| 	CommonPackageManager | ||||
| 	CommonRPM | ||||
| 	rootCmd string | ||||
| } | ||||
|  | ||||
| func (*APTRpm) Exists() bool { | ||||
| 	cmd := exec.Command("apt-config", "dump") | ||||
| 	output, err := cmd.Output() | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| func NewAPTRpm() *APTRpm { | ||||
| 	return &APTRpm{ | ||||
| 		CommonPackageManager: CommonPackageManager{ | ||||
| 			noConfirmArg: "-y", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	return strings.Contains(string(output), "RPM") | ||||
| } | ||||
|  | ||||
| func (*APTRpm) Name() string { | ||||
| @@ -46,8 +44,14 @@ func (*APTRpm) Format() string { | ||||
| 	return "rpm" | ||||
| } | ||||
|  | ||||
| func (a *APTRpm) SetRootCmd(s string) { | ||||
| 	a.rootCmd = s | ||||
| func (*APTRpm) Exists() bool { | ||||
| 	cmd := exec.Command("apt-config", "dump") | ||||
| 	output, err := cmd.Output() | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	return strings.Contains(string(output), "RPM") | ||||
| } | ||||
|  | ||||
| func (a *APTRpm) Sync(opts *Opts) error { | ||||
| @@ -66,6 +70,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) | ||||
| @@ -105,20 +110,3 @@ func (a *APTRpm) UpgradeAll(opts *Opts) error { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (a *APTRpm) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
| 	} | ||||
|  | ||||
| 	if opts.NoConfirm { | ||||
| 		cmd.Args = append(cmd.Args, "-y") | ||||
| 	} | ||||
|  | ||||
| 	return cmd | ||||
| } | ||||
|   | ||||
							
								
								
									
										35
									
								
								pkg/manager/common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								pkg/manager/common.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package manager | ||||
|  | ||||
| import "os/exec" | ||||
|  | ||||
| type CommonPackageManager struct { | ||||
| 	noConfirmArg string | ||||
| } | ||||
|  | ||||
| func (m *CommonPackageManager) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	cmd := exec.Command(mgrCmd) | ||||
| 	cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 	cmd.Args = append(cmd.Args, args...) | ||||
|  | ||||
| 	if opts.NoConfirm { | ||||
| 		cmd.Args = append(cmd.Args, m.noConfirmArg) | ||||
| 	} | ||||
|  | ||||
| 	return cmd | ||||
| } | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * ALR - Any Linux Repository | ||||
|  * ALR - Любой Linux Репозиторий | ||||
|  * Copyright (C) 2024 Евгений Храмов | ||||
|  * | ||||
|  * This program является свободным: вы можете распространять его и/или изменять | ||||
|  * на условиях GNU General Public License, опубликованной Free Software Foundation, | ||||
|  * либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии. | ||||
|  * | ||||
|  * Это программное обеспечение распространяется в надежде, что оно будет полезным, | ||||
|  * но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; без подразумеваемой гарантии | ||||
|  * КОММЕРЧЕСКОЙ ПРИГОДНОСТИ или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ. | ||||
|  * Подробности см. в GNU General Public License. | ||||
|  * | ||||
|  * Вы должны были получить копию GNU General Public License | ||||
|  * вместе с этой программой. Если нет, см. <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||
| // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов. | ||||
| // | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package manager | ||||
|  | ||||
| @@ -23,33 +24,32 @@ import ( | ||||
| 	"os/exec" | ||||
| ) | ||||
|  | ||||
| // DNF представляет менеджер пакетов DNF | ||||
| type DNF struct { | ||||
| 	CommonPackageManager | ||||
| 	CommonRPM | ||||
| 	rootCmd string // rootCmd хранит команду, используемую для выполнения команд с правами root | ||||
| } | ||||
|  | ||||
| // Exists проверяет, доступен ли DNF в системе, возвращает true если да | ||||
| func NewDNF() *DNF { | ||||
| 	return &DNF{ | ||||
| 		CommonPackageManager: CommonPackageManager{ | ||||
| 			noConfirmArg: "-y", | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (*DNF) Exists() bool { | ||||
| 	_, err := exec.LookPath("dnf") | ||||
| 	return err == nil | ||||
| } | ||||
|  | ||||
| // Name возвращает имя менеджера пакетов, в данном случае "dnf" | ||||
| func (*DNF) Name() string { | ||||
| 	return "dnf" | ||||
| } | ||||
|  | ||||
| // Format возвращает формат пакетов "rpm", используемый DNF | ||||
| func (*DNF) Format() string { | ||||
| 	return "rpm" | ||||
| } | ||||
|  | ||||
| // SetRootCmd устанавливает команду, используемую для выполнения операций с правами root | ||||
| func (d *DNF) SetRootCmd(s string) { | ||||
| 	d.rootCmd = s | ||||
| } | ||||
|  | ||||
| // Sync выполняет upgrade всех установленных пакетов, обновляя их до более новых версий | ||||
| func (d *DNF) Sync(opts *Opts) error { | ||||
| 	opts = ensureOpts(opts) // Гарантирует, что opts не равен nil и содержит допустимые значения | ||||
| @@ -118,21 +118,3 @@ func (d *DNF) UpgradeAll(opts *Opts) error { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // getCmd создает и возвращает команду exec.Cmd для менеджера пакетов DNF | ||||
| func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(d.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
| 	} | ||||
|  | ||||
| 	if opts.NoConfirm { | ||||
| 		cmd.Args = append(cmd.Args, "-y") // Добавляет параметр автоматического подтверждения (-y) | ||||
| 	} | ||||
|  | ||||
| 	return cmd | ||||
| } | ||||
|   | ||||
| @@ -27,27 +27,22 @@ import ( | ||||
| var Args []string | ||||
|  | ||||
| type Opts struct { | ||||
| 	AsRoot    bool | ||||
| 	NoConfirm bool | ||||
| 	Args      []string | ||||
| } | ||||
|  | ||||
| var DefaultOpts = &Opts{ | ||||
| 	AsRoot:    true, | ||||
| 	NoConfirm: false, | ||||
| } | ||||
|  | ||||
| // DefaultRootCmd is the command used for privilege elevation by default | ||||
| var DefaultRootCmd = "sudo" | ||||
|  | ||||
| var managers = []Manager{ | ||||
| 	&Pacman{}, | ||||
| 	&APT{}, | ||||
| 	&DNF{}, | ||||
| 	&YUM{}, | ||||
| 	&APK{}, | ||||
| 	&Zypper{}, | ||||
| 	&APTRpm{}, | ||||
| 	NewPacman(), | ||||
| 	NewAPT(), | ||||
| 	NewDNF(), | ||||
| 	NewYUM(), | ||||
| 	NewAPK(), | ||||
| 	NewZypper(), | ||||
| 	NewAPTRpm(), | ||||
| } | ||||
|  | ||||
| // Register registers a new package manager | ||||
| @@ -64,8 +59,7 @@ type Manager interface { | ||||
| 	Format() string | ||||
| 	// Returns true if the package manager exists on the system. | ||||
| 	Exists() bool | ||||
| 	// Sets the command used to elevate privileges. Defaults to DefaultRootCmd. | ||||
| 	SetRootCmd(string) | ||||
|  | ||||
| 	// Sync fetches repositories without installing anything | ||||
| 	Sync(*Opts) error | ||||
| 	// Install installs packages | ||||
| @@ -104,18 +98,10 @@ func Get(name string) Manager { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // getRootCmd returns rootCmd if it's not empty, otherwise returns DefaultRootCmd | ||||
| func getRootCmd(rootCmd string) string { | ||||
| 	if rootCmd != "" { | ||||
| 		return rootCmd | ||||
| 	} | ||||
| 	return DefaultRootCmd | ||||
| } | ||||
|  | ||||
| func setCmdEnv(cmd *exec.Cmd) { | ||||
| 	cmd.Env = os.Environ() | ||||
| 	cmd.Stdin = os.Stdin | ||||
| 	cmd.Stdout = os.Stdout | ||||
| 	cmd.Stdout = os.Stderr | ||||
| 	cmd.Stderr = os.Stderr | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -28,7 +28,15 @@ import ( | ||||
|  | ||||
| // Pacman represents the Pacman package manager | ||||
| type Pacman struct { | ||||
| 	rootCmd string | ||||
| 	CommonPackageManager | ||||
| } | ||||
|  | ||||
| func NewPacman() *Pacman { | ||||
| 	return &Pacman{ | ||||
| 		CommonPackageManager: CommonPackageManager{ | ||||
| 			noConfirmArg: "--noconfirm", | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (*Pacman) Exists() bool { | ||||
| @@ -44,10 +52,6 @@ func (*Pacman) Format() string { | ||||
| 	return "archlinux" | ||||
| } | ||||
|  | ||||
| func (p *Pacman) SetRootCmd(s string) { | ||||
| 	p.rootCmd = s | ||||
| } | ||||
|  | ||||
| func (p *Pacman) Sync(opts *Opts) error { | ||||
| 	opts = ensureOpts(opts) | ||||
| 	cmd := p.getCmd(opts, "pacman", "-Sy") | ||||
| @@ -156,20 +160,3 @@ func (p *Pacman) IsInstalled(pkg string) (bool, error) { | ||||
| 	} | ||||
| 	return true, nil | ||||
| } | ||||
|  | ||||
| func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(p.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
| 	} | ||||
|  | ||||
| 	if opts.NoConfirm { | ||||
| 		cmd.Args = append(cmd.Args, "--noconfirm") | ||||
| 	} | ||||
|  | ||||
| 	return cmd | ||||
| } | ||||
|   | ||||
| @@ -26,9 +26,16 @@ import ( | ||||
|  | ||||
| // YUM represents the YUM package manager | ||||
| type YUM struct { | ||||
| 	CommonPackageManager | ||||
| 	CommonRPM | ||||
| } | ||||
|  | ||||
| 	rootCmd string | ||||
| func NewYUM() *YUM { | ||||
| 	return &YUM{ | ||||
| 		CommonPackageManager: CommonPackageManager{ | ||||
| 			noConfirmArg: "-y", | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (*YUM) Exists() bool { | ||||
| @@ -44,10 +51,6 @@ func (*YUM) Format() string { | ||||
| 	return "rpm" | ||||
| } | ||||
|  | ||||
| func (y *YUM) SetRootCmd(s string) { | ||||
| 	y.rootCmd = s | ||||
| } | ||||
|  | ||||
| func (y *YUM) Sync(opts *Opts) error { | ||||
| 	opts = ensureOpts(opts) | ||||
| 	cmd := y.getCmd(opts, "yum", "upgrade") | ||||
| @@ -110,20 +113,3 @@ func (y *YUM) UpgradeAll(opts *Opts) error { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (y *YUM) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(y.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
| 	} | ||||
|  | ||||
| 	if opts.NoConfirm { | ||||
| 		cmd.Args = append(cmd.Args, "-y") | ||||
| 	} | ||||
|  | ||||
| 	return cmd | ||||
| } | ||||
|   | ||||
| @@ -26,8 +26,16 @@ import ( | ||||
|  | ||||
| // Zypper represents the Zypper package manager | ||||
| type Zypper struct { | ||||
| 	CommonPackageManager | ||||
| 	CommonRPM | ||||
| 	rootCmd string | ||||
| } | ||||
|  | ||||
| func NewZypper() *YUM { | ||||
| 	return &YUM{ | ||||
| 		CommonPackageManager: CommonPackageManager{ | ||||
| 			noConfirmArg: "-y", | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (*Zypper) Exists() bool { | ||||
| @@ -43,10 +51,6 @@ func (*Zypper) Format() string { | ||||
| 	return "rpm" | ||||
| } | ||||
|  | ||||
| func (z *Zypper) SetRootCmd(s string) { | ||||
| 	z.rootCmd = s | ||||
| } | ||||
|  | ||||
| func (z *Zypper) Sync(opts *Opts) error { | ||||
| 	opts = ensureOpts(opts) | ||||
| 	cmd := z.getCmd(opts, "zypper", "refresh") | ||||
| @@ -109,20 +113,3 @@ func (z *Zypper) UpgradeAll(opts *Opts) error { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (z *Zypper) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(z.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
| 	} | ||||
|  | ||||
| 	if opts.NoConfirm { | ||||
| 		cmd.Args = append(cmd.Args, "-y") | ||||
| 	} | ||||
|  | ||||
| 	return cmd | ||||
| } | ||||
|   | ||||
| @@ -67,7 +67,7 @@ type action struct { | ||||
| // If repos is set to nil, the repos in the ALR config will be used. | ||||
| func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { | ||||
| 	if repos == nil { | ||||
| 		repos = rs.cfg.Repos(ctx) | ||||
| 		repos = rs.cfg.Repos() | ||||
| 	} | ||||
|  | ||||
| 	for _, repo := range repos { | ||||
| @@ -77,7 +77,7 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { | ||||
| 		} | ||||
|  | ||||
| 		slog.Info(gotext.Get("Pulling repository"), "name", repo.Name) | ||||
| 		repoDir := filepath.Join(rs.cfg.GetPaths(ctx).RepoDir, repo.Name) | ||||
| 		repoDir := filepath.Join(rs.cfg.GetPaths().RepoDir, repo.Name) | ||||
|  | ||||
| 		var repoFS billy.Filesystem | ||||
| 		gitDir := filepath.Join(repoDir, ".git") | ||||
| @@ -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), | ||||
| 	) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -32,13 +32,13 @@ import ( | ||||
|  | ||||
| type TestALRConfig struct{} | ||||
|  | ||||
| func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths { | ||||
| func (c *TestALRConfig) GetPaths() *config.Paths { | ||||
| 	return &config.Paths{ | ||||
| 		DBPath: ":memory:", | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo { | ||||
| func (c *TestALRConfig) Repos() []types.Repo { | ||||
| 	return []types.Repo{ | ||||
| 		{ | ||||
| 			Name: "test", | ||||
|   | ||||
| @@ -44,7 +44,7 @@ type TestALRConfig struct { | ||||
| 	PkgsDir  string | ||||
| } | ||||
|  | ||||
| func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths { | ||||
| func (c *TestALRConfig) GetPaths() *config.Paths { | ||||
| 	return &config.Paths{ | ||||
| 		DBPath:   ":memory:", | ||||
| 		CacheDir: c.CacheDir, | ||||
| @@ -53,7 +53,7 @@ func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo { | ||||
| func (c *TestALRConfig) Repos() []types.Repo { | ||||
| 	return []types.Repo{} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -17,16 +17,14 @@ | ||||
| package repos | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"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" | ||||
| ) | ||||
|  | ||||
| type Config interface { | ||||
| 	GetPaths(ctx context.Context) *config.Paths | ||||
| 	Repos(ctx context.Context) []types.Repo | ||||
| 	GetPaths() *config.Paths | ||||
| 	Repos() []types.Repo | ||||
| } | ||||
|  | ||||
| type Repos struct { | ||||
|   | ||||
| @@ -79,26 +79,22 @@ type PackageInfo struct { | ||||
| } | ||||
|  | ||||
| func (inf *PackageInfo) ToPackage(repoName string) *db.Package { | ||||
| 	return &db.Package{ | ||||
| 		Version:       inf.Version, | ||||
| 		Release:       inf.Release, | ||||
| 		Epoch:         inf.Epoch, | ||||
| 		Architectures: inf.Architectures, | ||||
| 		Licenses:      inf.Licenses, | ||||
| 		Provides:      inf.Provides, | ||||
| 		Conflicts:     inf.Conflicts, | ||||
| 		Replaces:      inf.Replaces, | ||||
| 		Description:   db.NewJSON(map[string]string{}), | ||||
| 		Homepage:      db.NewJSON(map[string]string{}), | ||||
| 		Maintainer:    db.NewJSON(map[string]string{}), | ||||
| 		Depends:       db.NewJSON(map[string][]string{}), | ||||
| 		BuildDepends:  db.NewJSON(map[string][]string{}), | ||||
| 		Repository:    repoName, | ||||
| 	} | ||||
| 	pkg := EmptyPackage(repoName) | ||||
| 	pkg.Version = inf.Version | ||||
| 	pkg.Release = inf.Release | ||||
| 	pkg.Epoch = inf.Epoch | ||||
| 	pkg.Architectures = inf.Architectures | ||||
| 	pkg.Licenses = inf.Licenses | ||||
| 	pkg.Provides = inf.Provides | ||||
| 	pkg.Conflicts = inf.Conflicts | ||||
| 	pkg.Replaces = inf.Replaces | ||||
| 	return pkg | ||||
| } | ||||
|  | ||||
| func EmptyPackage(repoName string) *db.Package { | ||||
| 	return &db.Package{ | ||||
| 		Group:        db.NewJSON(map[string]string{}), | ||||
| 		Summary:      db.NewJSON(map[string]string{}), | ||||
| 		Description:  db.NewJSON(map[string]string{}), | ||||
| 		Homepage:     db.NewJSON(map[string]string{}), | ||||
| 		Maintainer:   db.NewJSON(map[string]string{}), | ||||
| @@ -114,6 +110,8 @@ var overridable = map[string]string{ | ||||
| 	"desc":       "Description", | ||||
| 	"homepage":   "Homepage", | ||||
| 	"maintainer": "Maintainer", | ||||
| 	"group":      "Group", | ||||
| 	"summary":    "Summary", | ||||
| } | ||||
|  | ||||
| func resolveOverrides(runner *interp.Runner, pkg *db.Package) { | ||||
|   | ||||
							
								
								
									
										146
									
								
								repo.go
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								repo.go
									
									
									
									
									
								
							| @@ -20,19 +20,17 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/pelletier/go-toml/v2" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"golang.org/x/exp/slices" | ||||
|  | ||||
| 	"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/cliutils" | ||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||
| ) | ||||
|  | ||||
| func AddRepoCmd() *cli.Command { | ||||
| @@ -55,52 +53,57 @@ func AddRepoCmd() *cli.Command { | ||||
| 			}, | ||||
| 		}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
| 			if err := utils.ExitIfNotRoot(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			name := c.String("name") | ||||
| 			repoURL := c.String("url") | ||||
|  | ||||
| 			cfg := config.New() | ||||
| 			reposSlice := cfg.Repos(ctx) | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			cfg := deps.Cfg | ||||
|  | ||||
| 			reposSlice := cfg.Repos() | ||||
| 			for _, repo := range reposSlice { | ||||
| 				if repo.URL == repoURL { | ||||
| 					slog.Error("Repo already exists", "name", repo.Name) | ||||
| 					os.Exit(1) | ||||
| 				if repo.URL == repoURL || repo.Name == name { | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" already exists", repo.Name), nil) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			reposSlice = append(reposSlice, types.Repo{ | ||||
| 				Name: name, | ||||
| 				URL:  repoURL, | ||||
| 			}) | ||||
| 			cfg.SetRepos(reposSlice) | ||||
|  | ||||
| 			cfg.SetRepos(ctx, reposSlice) | ||||
|  | ||||
| 			cfgFl, err := os.Create(cfg.GetPaths(ctx).ConfigPath) | ||||
| 			err = cfg.SaveUserConfig() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error opening config file"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error saving config"), err) | ||||
| 			} | ||||
|  | ||||
| 			err = cfg.Save(cfgFl) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error encoding config"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			db := database.New(cfg) | ||||
| 			err = db.Init(ctx) | ||||
| 			deps, err = appbuilder. | ||||
| 				New(ctx). | ||||
| 				UseConfig(cfg). | ||||
| 				WithDB(). | ||||
| 				WithReposForcePull(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||
| 			} | ||||
|  | ||||
| 			rs := repos.New(cfg, db) | ||||
| 			err = rs.Pull(ctx, cfg.Repos(ctx)) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| @@ -121,14 +124,28 @@ func RemoveRepoCmd() *cli.Command { | ||||
| 			}, | ||||
| 		}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			if err := utils.ExitIfNotRoot(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 			name := c.String("name") | ||||
| 			cfg := config.New() | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			cfg := deps.Cfg | ||||
|  | ||||
| 			found := false | ||||
| 			index := 0 | ||||
| 			reposSlice := cfg.Repos(ctx) | ||||
| 			reposSlice := cfg.Repos() | ||||
| 			for i, repo := range reposSlice { | ||||
| 				if repo.Name == name { | ||||
| 					index = i | ||||
| @@ -136,39 +153,37 @@ func RemoveRepoCmd() *cli.Command { | ||||
| 				} | ||||
| 			} | ||||
| 			if !found { | ||||
| 				slog.Error(gotext.Get("Repo does not exist"), "name", name) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" does not exist", name), nil) | ||||
| 			} | ||||
|  | ||||
| 			cfg.SetRepos(ctx, slices.Delete(reposSlice, index, index+1)) | ||||
| 			cfg.SetRepos(slices.Delete(reposSlice, index, index+1)) | ||||
|  | ||||
| 			cfgFl, err := os.Create(cfg.GetPaths(ctx).ConfigPath) | ||||
| 			err = os.RemoveAll(filepath.Join(cfg.GetPaths().RepoDir, name)) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error opening config file"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error removing repo directory"), err) | ||||
| 			} | ||||
| 			err = cfg.SaveUserConfig() | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error saving config"), err) | ||||
| 			} | ||||
|  | ||||
| 			err = toml.NewEncoder(cfgFl).Encode(&cfg) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error encoding config"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = os.RemoveAll(filepath.Join(cfg.GetPaths(ctx).RepoDir, name)) | ||||
| 			deps, err = appbuilder. | ||||
| 				New(ctx). | ||||
| 				UseConfig(cfg). | ||||
| 				WithDB(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error removing repo directory"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			db := database.New(cfg) | ||||
| 			err = db.Init(ctx) | ||||
| 			err = deps.DB.DeletePkgs(ctx, "repository = ?", name) | ||||
| 			if err != nil { | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			err = db.DeletePkgs(ctx, "repository = ?", name) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error removing packages from database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error removing packages from database"), err) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| @@ -182,19 +197,22 @@ func RefreshCmd() *cli.Command { | ||||
| 		Usage:   gotext.Get("Pull all repositories that have changed"), | ||||
| 		Aliases: []string{"ref"}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			err := db.Init(ctx) | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithReposForcePull(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			rs := repos.New(cfg, db) | ||||
| 			err = rs.Pull(ctx, cfg.Repos(ctx)) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
|   | ||||
| @@ -96,53 +96,52 @@ if [ -z "$noPkgMgr" ]; then | ||||
|  | ||||
|   echo "Полученный список файлов:" | ||||
|   echo "$fileList" | ||||
|  | ||||
|   if [ "$pkgMgr" == "pacman" ]; then | ||||
|     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.pkg.tar.zst' | sort -V | tail -n 1) | ||||
|   elif [ "$pkgMgr" == "apt" ]; then | ||||
|     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.amd64.deb' | sort -V | tail -n 1) | ||||
|   elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then | ||||
| if [ "$pkgMgr" == "pacman" ]; then | ||||
|     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.pkg\.tar\.zst' | sort -V | tail -n 1) | ||||
| elif [ "$pkgMgr" == "apt" ]; then | ||||
|     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.amd64\.deb' | sort -V | tail -n 1) | ||||
| elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then | ||||
|     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.x86_64\.rpm' | grep -v 'alt1' | sort -V | tail -n 1) | ||||
|   elif [[ "$pkgMgr" == "apt-get"  ]]; then | ||||
|     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*alt1.x86_64.rpm' | sort -V | tail -n 1) | ||||
|  | ||||
|   else | ||||
|     error "Не поддерживаемый менеджер пакетов для автоматической установки" | ||||
|   fi | ||||
|  | ||||
|   if [ -z "$latestFile" ]; then | ||||
|     error "Не удалось найти соответствующий пакет для $pkgMgr" | ||||
|   fi | ||||
|  | ||||
|   info "Найдена последняя версия ALR: $latestFile" | ||||
|  | ||||
|   url="https://plemya-x.ru/$latestFile" | ||||
|   fname="$(mktemp -u -p /tmp "alr.XXXXXXXXXX").${pkgFormat}" | ||||
|  | ||||
|   info "Загрузка пакета ALR" | ||||
|   curl -L $url -o $fname | ||||
|  | ||||
|   if [ ! -f "$fname" ]; then | ||||
|     error "Ошибка загрузки пакета ALR" | ||||
|   fi | ||||
|  | ||||
|   info "Установка пакета ALR" | ||||
|   installPkg $pkgMgr $fname | ||||
|  | ||||
|   info "Очистка" | ||||
|   rm $fname | ||||
|  | ||||
|   info "Готово!" | ||||
| elif [ "$pkgMgr" == "apt-get" ]; then | ||||
|     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*-alt[0-9]+\.x86_64\.rpm' | sort -V | tail -n 1) | ||||
| else | ||||
|   info "Клонирование репозитория ALR" | ||||
|   git clone https://gitea.plemya-x.ru/xpamych/ALR.git /tmp/alr | ||||
|  | ||||
|   info "Установка ALR" | ||||
|   cd /tmp/alr | ||||
|   sudo make install | ||||
|  | ||||
|   info "Очистка репозитория ALR" | ||||
|   rm -rf /tmp/alr | ||||
|  | ||||
|   info "Все задачи выполнены!" | ||||
| error "Не поддерживаемый менеджер пакетов для автоматической установки" | ||||
| fi | ||||
|  | ||||
| if [ -z "$latestFile" ]; then | ||||
| error "Не удалось найти соответствующий пакет для $pkgMgr" | ||||
| fi | ||||
|  | ||||
| info "Найдена последняя версия ALR: $latestFile" | ||||
|  | ||||
| url="https://plemya-x.ru/$latestFile" | ||||
| fname="$(mktemp -u -p /tmp "alr.XXXXXXXXXX").${pkgFormat}" | ||||
|  | ||||
| info "Загрузка пакета ALR" | ||||
| curl -L $url -o $fname | ||||
|  | ||||
| if [ ! -f "$fname" ]; then | ||||
| error "Ошибка загрузки пакета ALR" | ||||
| fi | ||||
|  | ||||
| info "Установка пакета ALR" | ||||
| installPkg $pkgMgr $fname | ||||
|  | ||||
| info "Очистка" | ||||
| rm $fname | ||||
|  | ||||
| info "Готово!" | ||||
|  | ||||
| else | ||||
| info "Клонирование репозитория ALR" | ||||
| git clone https://gitea.plemya-x.ru/xpamych/ALR.git /tmp/alr | ||||
|  | ||||
| info "Установка ALR" | ||||
| cd /tmp/alr | ||||
| sudo make install | ||||
|  | ||||
| info "Очистка репозитория ALR" | ||||
| rm -rf /tmp/alr | ||||
|  | ||||
| info "Все задачи выполнены!" | ||||
| fi | ||||
|   | ||||
							
								
								
									
										58
									
								
								scripts/update-deps-cve.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								scripts/update-deps-cve.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| #!/bin/bash | ||||
| # ALR - Any Linux Repository | ||||
| # Copyright (C) 2025 Евгений Храмов | ||||
| # | ||||
| # This program is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # This program is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU General Public License | ||||
| # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
|  | ||||
| VULNS_FILE="vulns.json" | ||||
| COMMIT_MSG_FILE="commit_msg.txt" | ||||
|  | ||||
| echo "Scanning for vulnerabilities with Trivy..." | ||||
| trivy fs --scanners vuln --format json . > "$VULNS_FILE" | ||||
|  | ||||
| echo "security: update vulnerable packages" > "$COMMIT_MSG_FILE" | ||||
| echo "" >> "$COMMIT_MSG_FILE" | ||||
| echo "Vulnerabilities detected by Trivy scan:" >> "$COMMIT_MSG_FILE" | ||||
|  | ||||
| echo "Processing vulnerabilities..." | ||||
| jq -r ' | ||||
|   .Results[].Vulnerabilities[] |  | ||||
|   select(.PkgName and .FixedVersion) |  | ||||
|   "\(.PkgName)|\(.FixedVersion)|\(.VulnerabilityID)" | ||||
| ' "$VULNS_FILE" | sort | uniq | while IFS="|" read -r pkg version cve; do | ||||
|   echo "- ${pkg} (${cve})" >> "$COMMIT_MSG_FILE" | ||||
|   echo "Updating ${pkg} to v${version} (${cve})..." | ||||
|   go get "${pkg}@v${version}" || echo "Failed to update ${pkg}" | ||||
| done | ||||
|  | ||||
| echo "Running go mod tidy..." | ||||
| go mod tidy | ||||
|  | ||||
| echo "Verifying fixes..." | ||||
| trivy fs --scanners vuln . | ||||
|  | ||||
| echo "" | ||||
| echo "Suggested commit message:" | ||||
| echo "------------------------" | ||||
| cat "$COMMIT_MSG_FILE" | ||||
| echo "------------------------" | ||||
|  | ||||
| rm "$VULNS_FILE" | ||||
|  | ||||
| git add go.mod go.sum | ||||
|  | ||||
| echo "" | ||||
| echo "To commit these changes, run:" | ||||
| echo "git commit -a -F $(pwd)/$COMMIT_MSG_FILE" | ||||
							
								
								
									
										100
									
								
								search.go
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								search.go
									
									
									
									
									
								
							| @@ -18,15 +18,19 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"text/template" | ||||
|  | ||||
| 	"github.com/jeandeaual/go-locale" | ||||
| 	"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/cliutils" | ||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search" | ||||
| ) | ||||
|  | ||||
| @@ -36,6 +40,11 @@ func SearchCmd() *cli.Command { | ||||
| 		Usage:   gotext.Get("Search packages"), | ||||
| 		Aliases: []string{"s"}, | ||||
| 		Flags: []cli.Flag{ | ||||
| 			&cli.BoolFlag{ | ||||
| 				Name:    "all", | ||||
| 				Aliases: []string{"a"}, | ||||
| 				Usage:   gotext.Get("Show all information, not just for the current distro"), | ||||
| 			}, | ||||
| 			&cli.StringFlag{ | ||||
| 				Name:    "name", | ||||
| 				Aliases: []string{"n"}, | ||||
| @@ -63,28 +72,50 @@ func SearchCmd() *cli.Command { | ||||
| 			}, | ||||
| 		}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			err := db.Init(ctx) | ||||
| 			defer db.Close() | ||||
|  | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			format := c.String("format") | ||||
| 			var tmpl *template.Template | ||||
| 			if format != "" { | ||||
| 				tmpl, err = template.New("format").Parse(format) | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 			var names []string | ||||
| 			all := c.Bool("all") | ||||
|  | ||||
| 			systemLang, err := locale.GetLanguage() | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Can't detect system language"), err) | ||||
| 			} | ||||
| 			if systemLang == "" { | ||||
| 				systemLang = "en" | ||||
| 			} | ||||
| 			if !all { | ||||
| 				info, err := distro.ParseOSRelease(ctx) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error parsing format template"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) | ||||
| 				} | ||||
| 				names, err = overrides.Resolve( | ||||
| 					info, | ||||
| 					overrides.DefaultOpts. | ||||
| 						WithLanguages([]string{systemLang}), | ||||
| 				) | ||||
| 				if err != nil { | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			s := search.New(db) | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			database := deps.DB | ||||
|  | ||||
| 			s := search.New(database) | ||||
|  | ||||
| 			packages, err := s.Search( | ||||
| 				ctx, | ||||
| @@ -96,20 +127,39 @@ func SearchCmd() *cli.Command { | ||||
| 					Build(), | ||||
| 			) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error parsing format template"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error while executing search"), err) | ||||
| 			} | ||||
|  | ||||
| 			format := c.String("format") | ||||
| 			var tmpl *template.Template | ||||
| 			if format != "" { | ||||
| 				tmpl, err = template.New("format").Parse(format) | ||||
| 				if err != nil { | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Error parsing format template"), err) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			for _, dbPkg := range packages { | ||||
| 				var pkg any | ||||
| 				if !all { | ||||
| 					pkg = overrides.ResolvePackage(&dbPkg, names) | ||||
| 				} else { | ||||
| 					pkg = &dbPkg | ||||
| 				} | ||||
|  | ||||
| 				if tmpl != nil { | ||||
| 					err = tmpl.Execute(os.Stdout, dbPkg) | ||||
| 					err = tmpl.Execute(os.Stdout, pkg) | ||||
| 					if err != nil { | ||||
| 						slog.Error(gotext.Get("Error executing template"), "err", err) | ||||
| 						os.Exit(1) | ||||
| 						return cliutils.FormatCliExit(gotext.Get("Error executing template"), err) | ||||
| 					} | ||||
| 					fmt.Println() | ||||
| 				} else { | ||||
| 					fmt.Println(dbPkg.Name) | ||||
| 					switch v := pkg.(type) { | ||||
| 					case *overrides.ResolvedPackage: | ||||
| 						fmt.Println(v.Name) | ||||
| 					case *db.Package: | ||||
| 						fmt.Println(v.Name) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
|   | ||||
							
								
								
									
										103
									
								
								upgrade.go
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								upgrade.go
									
									
									
									
									
								
							| @@ -23,21 +23,21 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"go.elara.ws/vercmp" | ||||
| 	"golang.org/x/exp/maps" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||
| 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | ||||
| 	"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" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search" | ||||
| ) | ||||
|  | ||||
| @@ -54,60 +54,77 @@ func UpgradeCmd() *cli.Command { | ||||
| 			}, | ||||
| 		}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			if err := utils.ExitIfNotRoot(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			installer, installerClose, err := build.GetSafeInstaller() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer installerClose() | ||||
|  | ||||
| 			if err := utils.ExitIfCantSetNoNewPrivs(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer scripterClose() | ||||
|  | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			rs := repos.New(cfg, db) | ||||
| 			err := db.Init(ctx) | ||||
| 			deps, err := appbuilder. | ||||
| 				New(ctx). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithRepos(). | ||||
| 				WithDistroInfo(). | ||||
| 				WithManager(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			info, err := distro.ParseOSRelease(ctx) | ||||
| 			builder, err := build.NewMainBuilder( | ||||
| 				deps.Cfg, | ||||
| 				deps.Manager, | ||||
| 				deps.Repos, | ||||
| 				scripter, | ||||
| 				installer, | ||||
| 			) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error parsing os-release file"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			mgr := manager.Detect() | ||||
| 			if mgr == nil { | ||||
| 				slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			if cfg.AutoPull(ctx) { | ||||
| 				err = rs.Pull(ctx, cfg.Repos(ctx)) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			updates, err := checkForUpdates(ctx, mgr, cfg, db, rs, info) | ||||
| 			updates, err := checkForUpdates(ctx, deps.Manager, deps.DB, deps.Info) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error checking for updates"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err) | ||||
| 			} | ||||
|  | ||||
| 			if len(updates) > 0 { | ||||
| 				builder := build.NewBuilder( | ||||
| 				err = builder.InstallALRPackages( | ||||
| 					ctx, | ||||
| 					types.BuildOpts{ | ||||
| 						Manager:     mgr, | ||||
| 						Clean:       c.Bool("clean"), | ||||
| 						Interactive: c.Bool("interactive"), | ||||
| 					&build.BuildArgs{ | ||||
| 						Opts: &types.BuildOpts{ | ||||
| 							Clean:       c.Bool("clean"), | ||||
| 							Interactive: c.Bool("interactive"), | ||||
| 						}, | ||||
| 						Info:       deps.Info, | ||||
| 						PkgFormat_: build.GetPkgFormat(deps.Manager), | ||||
| 					}, | ||||
| 					rs, | ||||
| 					info, | ||||
| 					cfg, | ||||
| 					updates, | ||||
| 				) | ||||
| 				builder.InstallPkgs(ctx, updates, nil, types.BuildOpts{ | ||||
| 					Manager:     mgr, | ||||
| 					Clean:       c.Bool("clean"), | ||||
| 					Interactive: c.Bool("interactive"), | ||||
| 				}) | ||||
| 				if err != nil { | ||||
| 					return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err) | ||||
| 				} | ||||
| 			} else { | ||||
| 				slog.Info(gotext.Get("There is nothing to do.")) | ||||
| 			} | ||||
| @@ -120,9 +137,7 @@ func UpgradeCmd() *cli.Command { | ||||
| func checkForUpdates( | ||||
| 	ctx context.Context, | ||||
| 	mgr manager.Manager, | ||||
| 	cfg *config.ALRConfig, | ||||
| 	db *database.Database, | ||||
| 	rs *repos.Repos, | ||||
| 	info *distro.OSRelease, | ||||
| ) ([]database.Package, error) { | ||||
| 	installed, err := mgr.ListInstalled(nil) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user