убрана лишняя зависимость 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 {
|
||||||
|
101
fix.go
101
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,12 +57,18 @@ 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 {
|
||||||
|
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)
|
return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
defer dir.Close()
|
defer dir.Close()
|
||||||
|
|
||||||
entries, err := dir.Readdirnames(-1)
|
entries, err := dir.Readdirnames(-1)
|
||||||
@@ -73,23 +79,106 @@ func FixCmd() *cli.Command {
|
|||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
fullPath := filepath.Join(paths.CacheDir, entry)
|
fullPath := filepath.Join(paths.CacheDir, entry)
|
||||||
|
|
||||||
|
// Пробуем сделать файлы доступными для записи
|
||||||
if err := makeWritableRecursive(fullPath); err != nil {
|
if err := makeWritableRecursive(fullPath); err != nil {
|
||||||
slog.Debug("Failed to make path writable", "path", fullPath, "error", err)
|
slog.Debug("Failed to make path writable", "path", fullPath, "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Пробуем удалить
|
||||||
err = os.RemoveAll(fullPath)
|
err = os.RemoveAll(fullPath)
|
||||||
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 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 {
|
||||||
|
// Если не получилось удалить, пробуем через 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 {
|
||||||
|
// Если не получилось, пробуем через 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)
|
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.
|
||||||
New(ctx).
|
New(ctx).
|
||||||
WithConfig().
|
WithConfig().
|
||||||
|
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