убрана лишняя зависимость bindfs и избыточное использование дополнительного пользователя alr
This commit is contained in:
		| @@ -47,7 +47,7 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Prepare for install |       - name: Prepare for install | ||||||
|         run: | |         run: | | ||||||
|           apt-get update && apt-get install -y libcap2-bin bindfs |           apt-get update | ||||||
|  |  | ||||||
|       - name: Build alr |       - name: Build alr | ||||||
|         env: |         env: | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -3,11 +3,12 @@ | |||||||
| /cmd/alr-api-server/alr-api-server | /cmd/alr-api-server/alr-api-server | ||||||
| /dist/ | /dist/ | ||||||
| /internal/config/version.txt | /internal/config/version.txt | ||||||
| .fleet | .fleet/ | ||||||
| .idea | .idea/ | ||||||
| .gigaide | .gigaide/ | ||||||
|  |  | ||||||
| *.out | *.out | ||||||
|  |  | ||||||
| e2e-tests/alr | e2e-tests/alr | ||||||
|  | CLAUDE.md | ||||||
| commit_msg.txt | commit_msg.txt | ||||||
							
								
								
									
										11
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								Makefile
									
									
									
									
									
								
							| @@ -49,17 +49,12 @@ install: \ | |||||||
| $(INSTALLED_BIN): $(BIN) | $(INSTALLED_BIN): $(BIN) | ||||||
| 	install -Dm755 $< $@ | 	install -Dm755 $< $@ | ||||||
| ifeq ($(CREATE_SYSTEM_RESOURCES),1) | ifeq ($(CREATE_SYSTEM_RESOURCES),1) | ||||||
| 	setcap cap_setuid,cap_setgid+ep $(INSTALLED_BIN) |  | ||||||
| 	@if id alr >/dev/null 2>&1; then \ |  | ||||||
| 		echo "User 'alr' already exists. Skipping."; \ |  | ||||||
| 	else \ |  | ||||||
| 		useradd -r -s /usr/sbin/nologin alr; \ |  | ||||||
| 	fi |  | ||||||
| 	@for dir in $(ROOT_DIRS); do \ | 	@for dir in $(ROOT_DIRS); do \ | ||||||
| 		install -d -o alr -g alr -m 755 $$dir; \ | 		install -d -m 775 $$dir; \ | ||||||
|  | 		chgrp wheel $$dir; \ | ||||||
| 	done | 	done | ||||||
| else | else | ||||||
| 	@echo "Skipping user and root dir creation (CREATE_SYSTEM_RESOURCES=0)" | 	@echo "Skipping root dir creation (CREATE_SYSTEM_RESOURCES=0)" | ||||||
| endif | endif | ||||||
|  |  | ||||||
| $(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION) | $(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION) | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								build.go
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								build.go
									
									
									
									
									
								
							| @@ -72,12 +72,6 @@ func BuildCmd() *cli.Command { | |||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			wd, wdCleanup, err := Mount(wd) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			defer wdCleanup() |  | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  |  | ||||||
| 			deps, err := appbuilder. | 			deps, err := appbuilder. | ||||||
| @@ -156,19 +150,9 @@ func BuildCmd() *cli.Command { | |||||||
| 				return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil) | 				return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if scriptArgs != nil { |  | ||||||
| 				scriptFile := filepath.Base(scriptArgs.Script) |  | ||||||
| 				newScriptDir, scriptDirCleanup, err := Mount(filepath.Dir(scriptArgs.Script)) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return err |  | ||||||
| 				} |  | ||||||
| 				defer scriptDirCleanup() |  | ||||||
| 				scriptArgs.Script = filepath.Join(newScriptDir, scriptFile) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			installer, installerClose, err := build.GetSafeInstaller() | 			installer, installerClose, err := build.GetSafeInstaller() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -176,9 +160,7 @@ func BuildCmd() *cli.Command { | |||||||
| 			} | 			} | ||||||
| 			defer installerClose() | 			defer installerClose() | ||||||
|  |  | ||||||
| 			if err := utils.ExitIfCantSetNoNewPrivs(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|   | |||||||
							
								
								
									
										131
									
								
								fix.go
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								fix.go
									
									
									
									
									
								
							| @@ -23,6 +23,7 @@ import ( | |||||||
| 	"io/fs" | 	"io/fs" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"os/exec" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  |  | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| @@ -38,9 +39,8 @@ func FixCmd() *cli.Command { | |||||||
| 		Name:  "fix", | 		Name:  "fix", | ||||||
| 		Usage: gotext.Get("Attempt to fix problems with ALR"), | 		Usage: gotext.Get("Attempt to fix problems with ALR"), | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | 			// Команда выполняется от текущего пользователя | ||||||
| 				return err | 			// При необходимости будет запрошен sudo для удаления файлов root | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  |  | ||||||
| @@ -57,37 +57,126 @@ func FixCmd() *cli.Command { | |||||||
|  |  | ||||||
| 			paths := cfg.GetPaths() | 			paths := cfg.GetPaths() | ||||||
|  |  | ||||||
| 			slog.Info(gotext.Get("Clearing cache directory")) | 			slog.Info(gotext.Get("Clearing cache and temporary directories")) | ||||||
|  |  | ||||||
|  | 			// Проверяем, существует ли директория кэша | ||||||
| 			dir, err := os.Open(paths.CacheDir) | 			dir, err := os.Open(paths.CacheDir) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err) | 				if os.IsNotExist(err) { | ||||||
| 			} | 					// Директория не существует, просто создадим её позже | ||||||
| 			defer dir.Close() | 					slog.Info(gotext.Get("Cache directory does not exist, will create it")) | ||||||
|  | 				} else { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err) | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				defer dir.Close() | ||||||
|  |  | ||||||
| 			entries, err := dir.Readdirnames(-1) | 				entries, err := dir.Readdirnames(-1) | ||||||
| 			if err != nil { | 				if err != nil { | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err) | 					return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err) | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			for _, entry := range entries { |  | ||||||
| 				fullPath := filepath.Join(paths.CacheDir, entry) |  | ||||||
|  |  | ||||||
| 				if err := makeWritableRecursive(fullPath); err != nil { |  | ||||||
| 					slog.Debug("Failed to make path writable", "path", fullPath, "error", err) |  | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				err = os.RemoveAll(fullPath) | 				for _, entry := range entries { | ||||||
|  | 					fullPath := filepath.Join(paths.CacheDir, entry) | ||||||
|  |  | ||||||
|  | 					// Пробуем сделать файлы доступными для записи | ||||||
|  | 					if err := makeWritableRecursive(fullPath); err != nil { | ||||||
|  | 						slog.Debug("Failed to make path writable", "path", fullPath, "error", err) | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Пробуем удалить | ||||||
|  | 					err = os.RemoveAll(fullPath) | ||||||
|  | 					if err != nil { | ||||||
|  | 						// Если не получилось удалить, пробуем через sudo | ||||||
|  | 						slog.Warn(gotext.Get("Unable to remove cache item (%s) as current user, trying with sudo", entry)) | ||||||
|  | 						 | ||||||
|  | 						sudoCmd := exec.Command("sudo", "rm", "-rf", fullPath) | ||||||
|  | 						if sudoErr := sudoCmd.Run(); sudoErr != nil { | ||||||
|  | 							// Если и через sudo не получилось, пропускаем с предупреждением | ||||||
|  | 							slog.Error(gotext.Get("Unable to remove cache item (%s)", entry), "error", err) | ||||||
|  | 							continue | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Очищаем временные директории | ||||||
|  | 			slog.Info(gotext.Get("Clearing temporary directory")) | ||||||
|  | 			tmpDir := "/tmp/alr" | ||||||
|  | 			if _, err := os.Stat(tmpDir); err == nil { | ||||||
|  | 				// Директория существует, пробуем очистить | ||||||
|  | 				err = os.RemoveAll(tmpDir) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err) | 					// Если не получилось удалить, пробуем через sudo | ||||||
|  | 					slog.Warn(gotext.Get("Unable to remove temporary directory as current user, trying with sudo")) | ||||||
|  | 					sudoCmd := exec.Command("sudo", "rm", "-rf", tmpDir) | ||||||
|  | 					if sudoErr := sudoCmd.Run(); sudoErr != nil { | ||||||
|  | 						slog.Error(gotext.Get("Unable to remove temporary directory"), "error", err) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Создаем базовый каталог /tmp/alr с владельцем root:wheel и правами 775 | ||||||
|  | 			err = utils.EnsureTempDirWithRootOwner(tmpDir, 0o775) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Warn(gotext.Get("Unable to create temporary directory"), "error", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Создаем каталог dl с правами для группы wheel | ||||||
|  | 			dlDir := filepath.Join(tmpDir, "dl") | ||||||
|  | 			err = utils.EnsureTempDirWithRootOwner(dlDir, 0o775) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Warn(gotext.Get("Unable to create download directory"), "error", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Создаем каталог pkgs с правами для группы wheel | ||||||
|  | 			pkgsDir := filepath.Join(tmpDir, "pkgs") | ||||||
|  | 			err = utils.EnsureTempDirWithRootOwner(pkgsDir, 0o775) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Warn(gotext.Get("Unable to create packages directory"), "error", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Исправляем права на все существующие файлы в /tmp/alr, если там что-то есть | ||||||
|  | 			if _, err := os.Stat(tmpDir); err == nil { | ||||||
|  | 				slog.Info(gotext.Get("Fixing permissions on temporary files")) | ||||||
|  | 				 | ||||||
|  | 				// Проверяем, есть ли файлы в директории | ||||||
|  | 				entries, err := os.ReadDir(tmpDir) | ||||||
|  | 				if err == nil && len(entries) > 0 { | ||||||
|  | 					fixCmd := exec.Command("sudo", "chown", "-R", "root:wheel", tmpDir) | ||||||
|  | 					if fixErr := fixCmd.Run(); fixErr != nil { | ||||||
|  | 						slog.Warn(gotext.Get("Unable to fix file ownership"), "error", fixErr) | ||||||
|  | 					} | ||||||
|  | 					 | ||||||
|  | 					fixCmd = exec.Command("sudo", "chmod", "-R", "2775", tmpDir) | ||||||
|  | 					if fixErr := fixCmd.Run(); fixErr != nil { | ||||||
|  | 						slog.Warn(gotext.Get("Unable to fix file permissions"), "error", fixErr) | ||||||
|  | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			slog.Info(gotext.Get("Rebuilding cache")) | 			slog.Info(gotext.Get("Rebuilding cache")) | ||||||
|  |  | ||||||
| 			err = os.MkdirAll(paths.CacheDir, 0o755) | 			// Пробуем создать директорию кэша | ||||||
|  | 			err = os.MkdirAll(paths.CacheDir, 0o775) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err) | 				// Если не получилось, пробуем через sudo с правильными правами для группы wheel | ||||||
|  | 				slog.Info(gotext.Get("Creating cache directory with sudo")) | ||||||
|  | 				sudoCmd := exec.Command("sudo", "mkdir", "-p", paths.CacheDir) | ||||||
|  | 				if sudoErr := sudoCmd.Run(); sudoErr != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err) | ||||||
|  | 				} | ||||||
|  | 				 | ||||||
|  | 				// Устанавливаем права 775 и группу wheel | ||||||
|  | 				chmodCmd := exec.Command("sudo", "chmod", "775", paths.CacheDir) | ||||||
|  | 				if chmodErr := chmodCmd.Run(); chmodErr != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Unable to set cache directory permissions"), chmodErr) | ||||||
|  | 				} | ||||||
|  | 				 | ||||||
|  | 				chgrpCmd := exec.Command("sudo", "chgrp", "wheel", paths.CacheDir) | ||||||
|  | 				if chgrpErr := chgrpCmd.Run(); chgrpErr != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Unable to set cache directory group"), chgrpErr) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			deps, err = appbuilder. | 			deps, err = appbuilder. | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								info.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								info.go
									
									
									
									
									
								
							| @@ -31,7 +31,6 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| ) | ) | ||||||
| @@ -48,9 +47,6 @@ func InfoCmd() *cli.Command { | |||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
| 			deps, err := appbuilder. | 			deps, err := appbuilder. | ||||||
| @@ -74,9 +70,7 @@ func InfoCmd() *cli.Command { | |||||||
| 			return nil | 			return nil | ||||||
| 		}), | 		}), | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | 			// Запуск от текущего пользователя | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			args := c.Args() | 			args := c.Args() | ||||||
| 			if args.Len() < 1 { | 			if args.Len() < 1 { | ||||||
|   | |||||||
| @@ -51,9 +51,6 @@ func InstallCmd() *cli.Command { | |||||||
| 				return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil) | 				return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			installer, installerClose, err := build.GetSafeInstaller() | 			installer, installerClose, err := build.GetSafeInstaller() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -61,9 +58,6 @@ func InstallCmd() *cli.Command { | |||||||
| 			} | 			} | ||||||
| 			defer installerClose() | 			defer installerClose() | ||||||
|  |  | ||||||
| 			if err := utils.ExitIfCantSetNoNewPrivs(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -116,9 +110,6 @@ func InstallCmd() *cli.Command { | |||||||
| 			return nil | 			return nil | ||||||
| 		}), | 		}), | ||||||
| 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
| 			deps, err := appbuilder. | 			deps, err := appbuilder. | ||||||
|   | |||||||
							
								
								
									
										163
									
								
								internal.go
									
									
									
									
									
								
							
							
						
						
									
										163
									
								
								internal.go
									
									
									
									
									
								
							| @@ -17,14 +17,8 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bufio" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" |  | ||||||
| 	"os/user" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"syscall" | 	"syscall" | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/go-hclog" | 	"github.com/hashicorp/go-hclog" | ||||||
| @@ -36,7 +30,6 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
| 	"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/logger" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| @@ -52,9 +45,6 @@ func InternalBuildCmd() *cli.Command { | |||||||
|  |  | ||||||
| 			slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid()) | 			slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid()) | ||||||
|  |  | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			cfg := config.New() | 			cfg := config.New() | ||||||
| 			err := cfg.Load() | 			err := cfg.Load() | ||||||
| @@ -92,9 +82,6 @@ func InternalReposCmd() *cli.Command { | |||||||
| 		Action: utils.RootNeededAction(func(ctx *cli.Context) error { | 		Action: utils.RootNeededAction(func(ctx *cli.Context) error { | ||||||
| 			logger.SetupForGoPlugin() | 			logger.SetupForGoPlugin() | ||||||
|  |  | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			deps, err := appbuilder. | 			deps, err := appbuilder. | ||||||
| 				New(ctx.Context). | 				New(ctx.Context). | ||||||
| @@ -129,16 +116,7 @@ func InternalInstallCmd() *cli.Command { | |||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			logger.SetupForGoPlugin() | 			logger.SetupForGoPlugin() | ||||||
|  |  | ||||||
| 			if err := utils.EnsureIsAlrUser(); err != nil { | 			// Запуск от текущего пользователя, повышение прав будет через sudo при необходимости | ||||||
| 				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. | 			deps, err := appbuilder. | ||||||
| 				New(c.Context). | 				New(c.Context). | ||||||
| @@ -175,143 +153,4 @@ func InternalInstallCmd() *cli.Command { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| 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 |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -44,9 +44,9 @@ var HandshakeConfig = plugin.HandshakeConfig{ | |||||||
|  |  | ||||||
| func setCommonCmdEnv(cmd *exec.Cmd) { | func setCommonCmdEnv(cmd *exec.Cmd) { | ||||||
| 	cmd.Env = []string{ | 	cmd.Env = []string{ | ||||||
| 		"HOME=/var/cache/alr", | 		"HOME=" + os.Getenv("HOME"), | ||||||
| 		"LOGNAME=alr", | 		"LOGNAME=" + os.Getenv("USER"), | ||||||
| 		"USER=alr", | 		"USER=" + os.Getenv("USER"), | ||||||
| 		"PATH=/usr/bin:/bin:/usr/local/bin", | 		"PATH=/usr/bin:/bin:/usr/local/bin", | ||||||
| 	} | 	} | ||||||
| 	for _, env := range os.Environ() { | 	for _, env := range os.Environ() { | ||||||
| @@ -102,9 +102,7 @@ func getSafeExecutor[T any](subCommand, pluginName string) (T, func(), error) { | |||||||
| 		Cmd:             cmd, | 		Cmd:             cmd, | ||||||
| 		Logger:          logger.GetHCLoggerAdapter(), | 		Logger:          logger.GetHCLoggerAdapter(), | ||||||
| 		SkipHostEnv:     true, | 		SkipHostEnv:     true, | ||||||
| 		UnixSocketConfig: &plugin.UnixSocketConfig{ | 		UnixSocketConfig: &plugin.UnixSocketConfig{}, | ||||||
| 			Group: "alr", |  | ||||||
| 		}, |  | ||||||
| 		SyncStderr: os.Stderr, | 		SyncStderr: os.Stderr, | ||||||
| 	}) | 	}) | ||||||
| 	rpcClient, err := client.Client() | 	rpcClient, err := client.Client() | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | ||||||
| ) | ) | ||||||
| @@ -74,7 +75,9 @@ func (s *SourceDownloader) DownloadSources( | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		opts.DlCache = dlcache.New(s.cfg.GetPaths().CacheDir) | 		// Используем временную директорию для загрузок | ||||||
|  | 		// dlcache.New добавит свой подкаталог "dl" внутри | ||||||
|  | 		opts.DlCache = dlcache.New(constants.TempDir) | ||||||
|  |  | ||||||
| 		err := dl.Download(ctx, opts) | 		err := dl.Download(ctx, opts) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| @@ -47,15 +48,21 @@ import ( | |||||||
|  |  | ||||||
| // Функция prepareDirs подготавливает директории для сборки. | // Функция prepareDirs подготавливает директории для сборки. | ||||||
| func prepareDirs(dirs types.Directories) error { | func prepareDirs(dirs types.Directories) error { | ||||||
| 	err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует | 	// Пробуем удалить базовую директорию, если она существует | ||||||
|  | 	err := os.RemoveAll(dirs.BaseDir) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// Если не можем удалить (например, принадлежит root), игнорируем | ||||||
|  | 		// и попробуем создать новые директории | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Создаем директории с правильным владельцем для /tmp/alr с setgid битом | ||||||
|  | 	err = utils.EnsureTempDirWithRootOwner(dirs.SrcDir, 0o2775) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	err = os.MkdirAll(dirs.SrcDir, 0o755) // Создаем директорию для источников | 	 | ||||||
| 	if err != nil { | 	// Создаем директорию для пакетов с setgid битом | ||||||
| 		return err | 	return utils.EnsureTempDirWithRootOwner(dirs.PkgDir, 0o2775) | ||||||
| 	} |  | ||||||
| 	return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Функция buildContents создает секцию содержимого пакета, которая содержит файлы, | // Функция buildContents создает секцию содержимого пакета, которая содержит файлы, | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ package config | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  |  | ||||||
| 	"github.com/goccy/go-yaml" | 	"github.com/goccy/go-yaml" | ||||||
| @@ -55,7 +56,12 @@ func defaultConfigKoanf() *koanf.Koanf { | |||||||
| 		"ignorePkgUpdates": []string{}, | 		"ignorePkgUpdates": []string{}, | ||||||
| 		"logLevel":         "info", | 		"logLevel":         "info", | ||||||
| 		"autoPull":         true, | 		"autoPull":         true, | ||||||
| 		"repos":            []types.Repo{}, | 		"repos": []types.Repo{ | ||||||
|  | 			{ | ||||||
|  | 				Name: "alr-default", | ||||||
|  | 				URL:  "https://gitea.plemya-x.ru/Plemya-x/alr-default.git", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 	if err := k.Load(confmap.Provider(defaults, "."), nil); err != nil { | 	if err := k.Load(confmap.Provider(defaults, "."), nil); err != nil { | ||||||
| 		panic(k) | 		panic(k) | ||||||
| @@ -98,8 +104,15 @@ func (c *ALRConfig) Load() error { | |||||||
| 	c.paths.UserConfigPath = constants.SystemConfigPath | 	c.paths.UserConfigPath = constants.SystemConfigPath | ||||||
| 	c.paths.CacheDir = constants.SystemCachePath | 	c.paths.CacheDir = constants.SystemCachePath | ||||||
| 	c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo") | 	c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo") | ||||||
| 	c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs") | 	c.paths.PkgsDir = filepath.Join(constants.TempDir, "pkgs")  // Перемещаем в /tmp/alr/pkgs | ||||||
| 	c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db") | 	c.paths.DBPath = filepath.Join(c.paths.CacheDir, "alr.db") | ||||||
|  |  | ||||||
|  | 	// Проверяем существование кэш-директории, но не пытаемся создать | ||||||
|  | 	if _, err := os.Stat(c.paths.CacheDir); err != nil { | ||||||
|  | 		if !os.IsNotExist(err) { | ||||||
|  | 			return fmt.Errorf("failed to check cache directory: %w", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,6 +19,6 @@ package constants | |||||||
| const ( | const ( | ||||||
| 	SystemConfigPath = "/etc/alr/alr.toml" | 	SystemConfigPath = "/etc/alr/alr.toml" | ||||||
| 	SystemCachePath  = "/var/cache/alr" | 	SystemCachePath  = "/var/cache/alr" | ||||||
| 	AlrRunDir        = "/var/run/alr" | 	TempDir          = "/tmp/alr" | ||||||
| 	PrivilegedGroup  = "wheel" | 	PrivilegedGroup  = "wheel" | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -21,7 +21,10 @@ package db | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"fmt" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  |  | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	_ "modernc.org/sqlite" | 	_ "modernc.org/sqlite" | ||||||
| @@ -54,6 +57,21 @@ func New(config Config) *Database { | |||||||
|  |  | ||||||
| func (d *Database) Connect() error { | func (d *Database) Connect() error { | ||||||
| 	dsn := d.config.GetPaths().DBPath | 	dsn := d.config.GetPaths().DBPath | ||||||
|  | 	 | ||||||
|  | 	// Проверяем директорию для БД | ||||||
|  | 	dbDir := filepath.Dir(dsn) | ||||||
|  | 	if _, err := os.Stat(dbDir); err != nil { | ||||||
|  | 		if os.IsNotExist(err) { | ||||||
|  | 			// Директория не существует - пытаемся создать | ||||||
|  | 			if mkErr := os.MkdirAll(dbDir, 0775); mkErr != nil { | ||||||
|  | 				// Не смогли создать - вернём ошибку, пользователь должен использовать alr fix | ||||||
|  | 				return fmt.Errorf("cache directory does not exist, please run 'alr fix' to create it: %w", mkErr) | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			return fmt.Errorf("failed to check database directory: %w", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
| 	engine, err := xorm.NewEngine("sqlite", dsn) | 	engine, err := xorm.NewEngine("sqlite", dsn) | ||||||
| 	// engine.SetLogLevel(log.LOG_DEBUG) | 	// engine.SetLogLevel(log.LOG_DEBUG) | ||||||
| 	// engine.ShowSQL(true) | 	// engine.ShowSQL(true) | ||||||
|   | |||||||
| @@ -17,12 +17,9 @@ | |||||||
| package utils | package utils | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" |  | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"os/user" | 	"os/user" | ||||||
| 	"strconv" |  | ||||||
| 	"syscall" |  | ||||||
|  |  | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| @@ -32,114 +29,12 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func GetUidGidAlrUserString() (string, string, error) { | // IsNotRoot проверяет, что текущий пользователь не является root | ||||||
| 	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 IsNotRoot() bool { | func IsNotRoot() bool { | ||||||
| 	return os.Getuid() != 0 | 	return os.Getuid() != 0 | ||||||
| } | } | ||||||
|  |  | ||||||
| func EnsureIsAlrUser() error { | // EnuseIsPrivilegedGroupMember проверяет, что пользователь является членом привилегированной группы (wheel) | ||||||
| 	uid, gid, err := GetUidGidAlrUser() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	newUid := syscall.Getuid() |  | ||||||
| 	if newUid != uid { |  | ||||||
| 		return errors.New("uid don't matches requested") |  | ||||||
| 	} |  | ||||||
| 	newGid := syscall.Getgid() |  | ||||||
| 	if newGid != gid { |  | ||||||
| 		return errors.New("gid don't matches requested") |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func EnuseIsPrivilegedGroupMember() error { | func EnuseIsPrivilegedGroupMember() error { | ||||||
| 	currentUser, err := user.Current() | 	currentUser, err := user.Current() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -164,26 +59,6 @@ func EnuseIsPrivilegedGroupMember() error { | |||||||
| 	return cliutils.FormatCliExit(gotext.Get("You need to be a %s member to perform this action", constants.PrivilegedGroup), 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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func RootNeededAction(f cli.ActionFunc) cli.ActionFunc { | func RootNeededAction(f cli.ActionFunc) cli.ActionFunc { | ||||||
| 	return func(ctx *cli.Context) error { | 	return func(ctx *cli.Context) error { | ||||||
| 		deps, err := appbuilder. | 		deps, err := appbuilder. | ||||||
|   | |||||||
| @@ -16,8 +16,44 @@ | |||||||
|  |  | ||||||
| package utils | package utils | ||||||
|  |  | ||||||
| import "golang.org/x/sys/unix" | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"golang.org/x/sys/unix" | ||||||
|  | ) | ||||||
|  |  | ||||||
| func NoNewPrivs() error { | func NoNewPrivs() error { | ||||||
| 	return unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) | 	return unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // EnsureTempDirWithRootOwner создает каталог в /tmp/alr с правами для группы wheel | ||||||
|  | // Все каталоги в /tmp/alr принадлежат root:wheel с правами 775 | ||||||
|  | // Для других каталогов использует стандартные права | ||||||
|  | func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error { | ||||||
|  | 	if strings.HasPrefix(path, "/tmp/alr") { | ||||||
|  | 		// Сначала создаем директорию обычным способом | ||||||
|  | 		err := os.MkdirAll(path, mode) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		// Все каталоги в /tmp/alr доступны для группы wheel | ||||||
|  | 		// Устанавливаем setgid бит (2775), чтобы новые файлы наследовали группу | ||||||
|  | 		permissions := "2775" | ||||||
|  | 		group := "wheel" | ||||||
|  | 		 | ||||||
|  | 		// Устанавливаем права с setgid битом | ||||||
|  | 		err = exec.Command("sudo", "chmod", permissions, path).Run() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		// Устанавливаем владельца root:wheel | ||||||
|  | 		return exec.Command("sudo", "chown", "root:"+group, path).Run() | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Для остальных каталогов обычное создание | ||||||
|  | 	return os.MkdirAll(path, mode) | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								list.go
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								list.go
									
									
									
									
									
								
							| @@ -35,7 +35,6 @@ import ( | |||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -60,9 +59,6 @@ func ListCmd() *cli.Command { | |||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								main.go
									
									
									
									
									
								
							| @@ -87,7 +87,6 @@ func GetApp() *cli.App { | |||||||
| 			// Internal commands | 			// Internal commands | ||||||
| 			InternalBuildCmd(), | 			InternalBuildCmd(), | ||||||
| 			InternalInstallCmd(), | 			InternalInstallCmd(), | ||||||
| 			InternalMountCmd(), |  | ||||||
| 			InternalReposCmd(), | 			InternalReposCmd(), | ||||||
| 		}, | 		}, | ||||||
| 		Before: func(c *cli.Context) error { | 		Before: func(c *cli.Context) error { | ||||||
|   | |||||||
| @@ -1,3 +1,19 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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/>. | ||||||
|  |  | ||||||
| // DO NOT EDIT MANUALLY. This file is generated. | // DO NOT EDIT MANUALLY. This file is generated. | ||||||
| package alrsh | package alrsh | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								pkg/dl/dl.go
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								pkg/dl/dl.go
									
									
									
									
									
								
							| @@ -280,14 +280,14 @@ func handleCache(cacheDir, dest, name string, t Type) (bool, error) { | |||||||
| 		cd.Close() | 		cd.Close() | ||||||
|  |  | ||||||
| 		if slices.Contains(names, name) { | 		if slices.Contains(names, name) { | ||||||
| 			err = os.Link(filepath.Join(cacheDir, name), dest) | 			err = linkOrCopy(filepath.Join(cacheDir, name), dest) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return false, err | 				return false, err | ||||||
| 			} | 			} | ||||||
| 			return true, nil | 			return true, nil | ||||||
| 		} | 		} | ||||||
| 	case TypeDir: | 	case TypeDir: | ||||||
| 		err := linkDir(cacheDir, dest) | 		err := linkOrCopyDir(cacheDir, dest) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return false, err | 			return false, err | ||||||
| 		} | 		} | ||||||
| @@ -296,8 +296,40 @@ func handleCache(cacheDir, dest, name string, t Type) (bool, error) { | |||||||
| 	return false, nil | 	return false, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Функция linkDir рекурсивно создает жесткие ссылки для файлов из каталога src в каталог dest | // linkOrCopy пытается создать жесткую ссылку, а если не получается - копирует файл | ||||||
| func linkDir(src, dest string) error { | func linkOrCopy(src, dest string) error { | ||||||
|  | 	err := os.Link(src, dest) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// Если не удалось создать ссылку, копируем файл | ||||||
|  | 		srcFile, err := os.Open(src) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		defer srcFile.Close() | ||||||
|  |  | ||||||
|  | 		destFile, err := os.Create(dest) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		defer destFile.Close() | ||||||
|  |  | ||||||
|  | 		_, err = io.Copy(destFile, srcFile) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Копируем права доступа | ||||||
|  | 		srcInfo, err := srcFile.Stat() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		return os.Chmod(dest, srcInfo.Mode()) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // linkOrCopyDir рекурсивно создает жесткие ссылки или копирует файлы из каталога src в каталог dest | ||||||
|  | func linkOrCopyDir(src, dest string) error { | ||||||
| 	return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { | 	return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| @@ -317,7 +349,7 @@ func linkDir(src, dest string) error { | |||||||
| 			return os.MkdirAll(newPath, info.Mode()) | 			return os.MkdirAll(newPath, info.Mode()) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return os.Link(path, newPath) | 		return linkOrCopy(path, newPath) | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ import ( | |||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Config interface { | type Config interface { | ||||||
| @@ -61,7 +62,8 @@ func (dc *DownloadCache) New(ctx context.Context, id string) (string, error) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err = os.MkdirAll(itemPath, 0o755) | 	// Используем специальную функцию для создания каталогов | ||||||
|  | 	err = utils.EnsureTempDirWithRootOwner(itemPath, 0o755) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -21,7 +21,6 @@ import ( | |||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func RefreshCmd() *cli.Command { | func RefreshCmd() *cli.Command { | ||||||
| @@ -30,9 +29,6 @@ func RefreshCmd() *cli.Command { | |||||||
| 		Usage:   gotext.Get("Pull all repositories that have changed"), | 		Usage:   gotext.Get("Pull all repositories that have changed"), | ||||||
| 		Aliases: []string{"ref"}, | 		Aliases: []string{"ref"}, | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								repo.go
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								repo.go
									
									
									
									
									
								
							| @@ -114,9 +114,6 @@ func RemoveRepoCmd() *cli.Command { | |||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error saving config"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error saving config"), err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			deps, err = appbuilder. | 			deps, err = appbuilder. | ||||||
| 				New(ctx). | 				New(ctx). | ||||||
|   | |||||||
| @@ -29,7 +29,6 @@ import ( | |||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/search" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/search" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| ) | ) | ||||||
| @@ -72,9 +71,6 @@ func SearchCmd() *cli.Command { | |||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  |  | ||||||
|   | |||||||
| @@ -55,9 +55,6 @@ func UpgradeCmd() *cli.Command { | |||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		Action: utils.RootNeededAction(func(c *cli.Context) error { | 		Action: utils.RootNeededAction(func(c *cli.Context) error { | ||||||
| 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			installer, installerClose, err := build.GetSafeInstaller() | 			installer, installerClose, err := build.GetSafeInstaller() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -65,9 +62,6 @@ func UpgradeCmd() *cli.Command { | |||||||
| 			} | 			} | ||||||
| 			defer installerClose() | 			defer installerClose() | ||||||
|  |  | ||||||
| 			if err := utils.ExitIfCantSetNoNewPrivs(); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user