убрана лишняя зависимость bindfs и избыточное использование дополнительного пользователя alr
This commit is contained in:
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
- name: Prepare for install
|
||||
run: |
|
||||
apt-get update && apt-get install -y libcap2-bin bindfs
|
||||
apt-get update
|
||||
|
||||
- name: Build alr
|
||||
env:
|
||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -3,11 +3,12 @@
|
||||
/cmd/alr-api-server/alr-api-server
|
||||
/dist/
|
||||
/internal/config/version.txt
|
||||
.fleet
|
||||
.idea
|
||||
.gigaide
|
||||
.fleet/
|
||||
.idea/
|
||||
.gigaide/
|
||||
|
||||
*.out
|
||||
|
||||
e2e-tests/alr
|
||||
CLAUDE.md
|
||||
commit_msg.txt
|
11
Makefile
11
Makefile
@@ -49,17 +49,12 @@ install: \
|
||||
$(INSTALLED_BIN): $(BIN)
|
||||
install -Dm755 $< $@
|
||||
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 \
|
||||
install -d -o alr -g alr -m 755 $$dir; \
|
||||
install -d -m 775 $$dir; \
|
||||
chgrp wheel $$dir; \
|
||||
done
|
||||
else
|
||||
@echo "Skipping user and root dir creation (CREATE_SYSTEM_RESOURCES=0)"
|
||||
@echo "Skipping root dir creation (CREATE_SYSTEM_RESOURCES=0)"
|
||||
endif
|
||||
|
||||
$(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)
|
||||
}
|
||||
|
||||
wd, wdCleanup, err := Mount(wd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wdCleanup()
|
||||
|
||||
ctx := c.Context
|
||||
|
||||
deps, err := appbuilder.
|
||||
@@ -156,19 +150,9 @@ func BuildCmd() *cli.Command {
|
||||
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()
|
||||
if err != nil {
|
||||
@@ -176,9 +160,7 @@ func BuildCmd() *cli.Command {
|
||||
}
|
||||
defer installerClose()
|
||||
|
||||
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
scripter, scripterClose, err := build.GetSafeScriptExecutor()
|
||||
if err != nil {
|
||||
|
131
fix.go
131
fix.go
@@ -23,6 +23,7 @@ import (
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
@@ -38,9 +39,8 @@ 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
|
||||
}
|
||||
// Команда выполняется от текущего пользователя
|
||||
// При необходимости будет запрошен sudo для удаления файлов root
|
||||
|
||||
ctx := c.Context
|
||||
|
||||
@@ -57,37 +57,126 @@ func FixCmd() *cli.Command {
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err)
|
||||
}
|
||||
defer dir.Close()
|
||||
if os.IsNotExist(err) {
|
||||
// Директория не существует, просто создадим её позже
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
entries, err := dir.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), 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 {
|
||||
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"))
|
||||
|
||||
err = os.MkdirAll(paths.CacheDir, 0o755)
|
||||
// Пробуем создать директорию кэша
|
||||
err = os.MkdirAll(paths.CacheDir, 0o775)
|
||||
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.
|
||||
|
8
info.go
8
info.go
@@ -31,7 +31,6 @@ import (
|
||||
"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/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/distro"
|
||||
)
|
||||
@@ -48,9 +47,6 @@ func InfoCmd() *cli.Command {
|
||||
},
|
||||
},
|
||||
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := c.Context
|
||||
deps, err := appbuilder.
|
||||
@@ -74,9 +70,7 @@ func InfoCmd() *cli.Command {
|
||||
return nil
|
||||
}),
|
||||
Action: func(c *cli.Context) error {
|
||||
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Запуск от текущего пользователя
|
||||
|
||||
args := c.Args()
|
||||
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)
|
||||
}
|
||||
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
installer, installerClose, err := build.GetSafeInstaller()
|
||||
if err != nil {
|
||||
@@ -61,9 +58,6 @@ func InstallCmd() *cli.Command {
|
||||
}
|
||||
defer installerClose()
|
||||
|
||||
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scripter, scripterClose, err := build.GetSafeScriptExecutor()
|
||||
if err != nil {
|
||||
@@ -116,9 +110,6 @@ func InstallCmd() *cli.Command {
|
||||
return nil
|
||||
}),
|
||||
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := c.Context
|
||||
deps, err := appbuilder.
|
||||
|
163
internal.go
163
internal.go
@@ -17,14 +17,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
@@ -36,7 +30,6 @@ import (
|
||||
"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/manager"
|
||||
"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())
|
||||
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := config.New()
|
||||
err := cfg.Load()
|
||||
@@ -92,9 +82,6 @@ func InternalReposCmd() *cli.Command {
|
||||
Action: utils.RootNeededAction(func(ctx *cli.Context) error {
|
||||
logger.SetupForGoPlugin()
|
||||
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deps, err := appbuilder.
|
||||
New(ctx.Context).
|
||||
@@ -129,16 +116,7 @@ func InternalInstallCmd() *cli.Command {
|
||||
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)
|
||||
}
|
||||
// Запуск от текущего пользователя, повышение прав будет через sudo при необходимости
|
||||
|
||||
deps, err := appbuilder.
|
||||
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) {
|
||||
cmd.Env = []string{
|
||||
"HOME=/var/cache/alr",
|
||||
"LOGNAME=alr",
|
||||
"USER=alr",
|
||||
"HOME=" + os.Getenv("HOME"),
|
||||
"LOGNAME=" + os.Getenv("USER"),
|
||||
"USER=" + os.Getenv("USER"),
|
||||
"PATH=/usr/bin:/bin:/usr/local/bin",
|
||||
}
|
||||
for _, env := range os.Environ() {
|
||||
@@ -102,9 +102,7 @@ func getSafeExecutor[T any](subCommand, pluginName string) (T, func(), error) {
|
||||
Cmd: cmd,
|
||||
Logger: logger.GetHCLoggerAdapter(),
|
||||
SkipHostEnv: true,
|
||||
UnixSocketConfig: &plugin.UnixSocketConfig{
|
||||
Group: "alr",
|
||||
},
|
||||
UnixSocketConfig: &plugin.UnixSocketConfig{},
|
||||
SyncStderr: os.Stderr,
|
||||
})
|
||||
rpcClient, err := client.Client()
|
||||
|
@@ -23,6 +23,7 @@ import (
|
||||
"os"
|
||||
"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/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)
|
||||
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/manager"
|
||||
"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/distro"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||
@@ -47,15 +48,21 @@ import (
|
||||
|
||||
// Функция prepareDirs подготавливает директории для сборки.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(dirs.SrcDir, 0o755) // Создаем директорию для источников
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов
|
||||
|
||||
// Создаем директорию для пакетов с setgid битом
|
||||
return utils.EnsureTempDirWithRootOwner(dirs.PkgDir, 0o2775)
|
||||
}
|
||||
|
||||
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
|
||||
|
@@ -21,6 +21,7 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
@@ -55,7 +56,12 @@ func defaultConfigKoanf() *koanf.Koanf {
|
||||
"ignorePkgUpdates": []string{},
|
||||
"logLevel": "info",
|
||||
"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 {
|
||||
panic(k)
|
||||
@@ -98,8 +104,15 @@ func (c *ALRConfig) Load() error {
|
||||
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.paths.PkgsDir = filepath.Join(constants.TempDir, "pkgs") // Перемещаем в /tmp/alr/pkgs
|
||||
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
|
||||
}
|
||||
|
@@ -19,6 +19,6 @@ package constants
|
||||
const (
|
||||
SystemConfigPath = "/etc/alr/alr.toml"
|
||||
SystemCachePath = "/var/cache/alr"
|
||||
AlrRunDir = "/var/run/alr"
|
||||
TempDir = "/tmp/alr"
|
||||
PrivilegedGroup = "wheel"
|
||||
)
|
||||
|
@@ -21,7 +21,10 @@ package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
_ "modernc.org/sqlite"
|
||||
@@ -54,6 +57,21 @@ func New(config Config) *Database {
|
||||
|
||||
func (d *Database) Connect() error {
|
||||
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.SetLogLevel(log.LOG_DEBUG)
|
||||
// engine.ShowSQL(true)
|
||||
|
@@ -17,12 +17,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -32,114 +29,12 @@ import (
|
||||
"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
|
||||
}
|
||||
|
||||
// IsNotRoot проверяет, что текущий пользователь не является root
|
||||
func IsNotRoot() bool {
|
||||
return os.Getuid() != 0
|
||||
}
|
||||
|
||||
func EnsureIsAlrUser() error {
|
||||
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
|
||||
}
|
||||
|
||||
// EnuseIsPrivilegedGroupMember проверяет, что пользователь является членом привилегированной группы (wheel)
|
||||
func EnuseIsPrivilegedGroupMember() error {
|
||||
currentUser, err := user.Current()
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
return func(ctx *cli.Context) error {
|
||||
deps, err := appbuilder.
|
||||
|
@@ -16,8 +16,44 @@
|
||||
|
||||
package utils
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func NoNewPrivs() error {
|
||||
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"
|
||||
"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/utils"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
)
|
||||
|
||||
@@ -60,9 +59,6 @@ func ListCmd() *cli.Command {
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := c.Context
|
||||
|
||||
|
1
main.go
1
main.go
@@ -87,7 +87,6 @@ func GetApp() *cli.App {
|
||||
// Internal commands
|
||||
InternalBuildCmd(),
|
||||
InternalInstallCmd(),
|
||||
InternalMountCmd(),
|
||||
InternalReposCmd(),
|
||||
},
|
||||
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.
|
||||
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()
|
||||
|
||||
if slices.Contains(names, name) {
|
||||
err = os.Link(filepath.Join(cacheDir, name), dest)
|
||||
err = linkOrCopy(filepath.Join(cacheDir, name), dest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
case TypeDir:
|
||||
err := linkDir(cacheDir, dest)
|
||||
err := linkOrCopyDir(cacheDir, dest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -296,8 +296,40 @@ func handleCache(cacheDir, dest, name string, t Type) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Функция linkDir рекурсивно создает жесткие ссылки для файлов из каталога src в каталог dest
|
||||
func linkDir(src, dest string) error {
|
||||
// linkOrCopy пытается создать жесткую ссылку, а если не получается - копирует файл
|
||||
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 {
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -317,7 +349,7 @@ func linkDir(src, dest string) error {
|
||||
return os.MkdirAll(newPath, info.Mode())
|
||||
}
|
||||
|
||||
return os.Link(path, newPath)
|
||||
return linkOrCopy(path, newPath)
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -25,6 +25,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||
)
|
||||
|
||||
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 {
|
||||
return "", err
|
||||
}
|
||||
|
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
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 {
|
||||
@@ -30,9 +29,6 @@ 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
|
||||
|
||||
|
3
repo.go
3
repo.go
@@ -114,9 +114,6 @@ func RemoveRepoCmd() *cli.Command {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
|
||||
}
|
||||
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deps, err = appbuilder.
|
||||
New(ctx).
|
||||
|
@@ -29,7 +29,6 @@ import (
|
||||
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/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/distro"
|
||||
)
|
||||
@@ -72,9 +71,6 @@ func SearchCmd() *cli.Command {
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := c.Context
|
||||
|
||||
|
@@ -55,9 +55,6 @@ func UpgradeCmd() *cli.Command {
|
||||
},
|
||||
},
|
||||
Action: utils.RootNeededAction(func(c *cli.Context) error {
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
installer, installerClose, err := build.GetSafeInstaller()
|
||||
if err != nil {
|
||||
@@ -65,9 +62,6 @@ func UpgradeCmd() *cli.Command {
|
||||
}
|
||||
defer installerClose()
|
||||
|
||||
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scripter, scripterClose, err := build.GetSafeScriptExecutor()
|
||||
if err != nil {
|
||||
|
Reference in New Issue
Block a user