9 Commits

Author SHA1 Message Date
9efebbc02a Исправление функционала создания дирректорий для работы ALR
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m11s
Create Release / changelog (push) Successful in 3m11s
2025-09-21 15:31:51 +03:00
ef41d682a1 Исправление функционала повышения привилегий
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m12s
Create Release / changelog (push) Successful in 3m8s
2025-09-21 15:04:42 +03:00
42f0d5e575 Исправление дублирования "alr" в названии пакета
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m6s
Create Release / changelog (push) Successful in 3m7s
2025-09-21 13:43:36 +03:00
7b9404a058 Исправление обработки зависимостей на debian-based
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m28s
Create Release / changelog (push) Successful in 3m6s
2025-09-21 12:36:48 +03:00
18e8dc3fbf Исправление логики определения привилегированной группы для debian производных дистрибутивов
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m28s
Create Release / changelog (push) Successful in 3m10s
2025-09-21 01:08:26 +03:00
9c0af83a20 Добавление вычисления SHA256 для архива и обновление версии и чексуммы
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m33s
2025-09-19 23:13:32 +03:00
4bd20d84ef Добавление логики поиска пакета с noarch
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m28s
2025-09-16 23:44:23 +03:00
8dea5e1e7f Улучшена логика создания конфига при новом запуске и при появлении новых опций (миграция)
All checks were successful
Pre-commit / pre-commit (push) Successful in 6m38s
Create Release / changelog (push) Successful in 3m4s
2025-09-11 23:29:24 +03:00
86a982478e Исправление PrepareDirs вызывался только если пакет действительно нужно собирать
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m43s
Create Release / changelog (push) Successful in 3m8s
2025-09-08 22:31:43 +03:00
14 changed files with 341 additions and 121 deletions

View File

@@ -78,12 +78,31 @@ jobs:
token: ${{ secrets.GITEAPUBLIC }} token: ${{ secrets.GITEAPUBLIC }}
path: alr-default path: alr-default
- name: Update version in alr-bin - name: Calculate checksum
run: | run: |
# Замените значения в файле с конфигурацией # Вычисляем SHA256 контрольную сумму архива
CHECKSUM=$(sha256sum alr-${{ env.VERSION }}-linux-x86_64.tar.gz | awk '{print $1}')
echo "Archive checksum: $CHECKSUM"
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV
- name: Update version and checksum in alr-bin
run: |
# Обновляем версию
sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh
sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh
# Обновляем контрольную сумму
sed -i "s/checksums=('[^']*')/checksums=('${{ env.CHECKSUM }}')/g" alr-default/alr-bin/alr.sh
- name: Commit and push changes to alr-default
run: |
cd alr-default
git config user.name "gitea"
git config user.email "admin@plemya-x.ru"
git add alr-bin/alr.sh
git commit -m "Обновление alr-bin до версии ${{ env.VERSION }}"
git push
- name: Install alr - name: Install alr
env: env:
CREATE_SYSTEM_RESOURCES: 0 CREATE_SYSTEM_RESOURCES: 0

View File

@@ -63,7 +63,7 @@ func BuildCmd() *cli.Command {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil { if err := utils.CheckUserPrivileges(); err != nil {
return err return err
} }

34
fix.go
View File

@@ -131,22 +131,22 @@ func FixCmd() *cli.Command {
} }
} }
// Создаем базовый каталог /tmp/alr с владельцем root:wheel и правами 775 // Создаем базовый каталог /tmp/alr с владельцем root:wheel и правами 2775
err = utils.EnsureTempDirWithRootOwner(tmpDir, 0o775) err = utils.EnsureTempDirWithRootOwner(tmpDir, 0o2775)
if err != nil { if err != nil {
slog.Warn(gotext.Get("Unable to create temporary directory"), "error", err) slog.Warn(gotext.Get("Unable to create temporary directory"), "error", err)
} }
// Создаем каталог dl с правами для группы wheel // Создаем каталог dl с правами для группы wheel
dlDir := filepath.Join(tmpDir, "dl") dlDir := filepath.Join(tmpDir, "dl")
err = utils.EnsureTempDirWithRootOwner(dlDir, 0o775) err = utils.EnsureTempDirWithRootOwner(dlDir, 0o2775)
if err != nil { if err != nil {
slog.Warn(gotext.Get("Unable to create download directory"), "error", err) slog.Warn(gotext.Get("Unable to create download directory"), "error", err)
} }
// Создаем каталог pkgs с правами для группы wheel // Создаем каталог pkgs с правами для группы wheel
pkgsDir := filepath.Join(tmpDir, "pkgs") pkgsDir := filepath.Join(tmpDir, "pkgs")
err = utils.EnsureTempDirWithRootOwner(pkgsDir, 0o775) err = utils.EnsureTempDirWithRootOwner(pkgsDir, 0o2775)
if err != nil { if err != nil {
slog.Warn(gotext.Get("Unable to create packages directory"), "error", err) slog.Warn(gotext.Get("Unable to create packages directory"), "error", err)
} }
@@ -158,7 +158,8 @@ func FixCmd() *cli.Command {
// Проверяем, есть ли файлы в директории // Проверяем, есть ли файлы в директории
entries, err := os.ReadDir(tmpDir) entries, err := os.ReadDir(tmpDir)
if err == nil && len(entries) > 0 { if err == nil && len(entries) > 0 {
fixCmd := execWithPrivileges("chown", "-R", "root:wheel", tmpDir) group := utils.GetPrivilegedGroup()
fixCmd := execWithPrivileges("chown", "-R", "root:"+group, tmpDir)
if fixErr := fixCmd.Run(); fixErr != nil { if fixErr := fixCmd.Run(); fixErr != nil {
slog.Warn(gotext.Get("Unable to fix file ownership"), "error", fixErr) slog.Warn(gotext.Get("Unable to fix file ownership"), "error", fixErr)
} }
@@ -172,26 +173,11 @@ func FixCmd() *cli.Command {
slog.Info(gotext.Get("Rebuilding cache")) slog.Info(gotext.Get("Rebuilding cache"))
// Пробуем создать директорию кэша // Создаем директорию кэша с правильными правами
err = os.MkdirAll(paths.CacheDir, 0o775) slog.Info(gotext.Get("Creating cache directory"))
err = utils.EnsureTempDirWithRootOwner(paths.CacheDir, 0o2775)
if err != nil { if err != nil {
// Если не получилось, пробуем через sudo с правильными правами для группы wheel return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err)
slog.Info(gotext.Get("Creating cache directory with sudo"))
sudoCmd := execWithPrivileges("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 := execWithPrivileges("chmod", "775", paths.CacheDir)
if chmodErr := chmodCmd.Run(); chmodErr != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to set cache directory permissions"), chmodErr)
}
chgrpCmd := execWithPrivileges("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.

View File

@@ -320,9 +320,9 @@ func (b *Builder) BuildPackage(
} }
var builtDeps []*BuiltDep var builtDeps []*BuiltDep
var remainingVars []*alrsh.Package
if !input.opts.Clean { if !input.opts.Clean {
var remainingVars []*alrsh.Package
for _, vars := range varsOfPackages { for _, vars := range varsOfPackages {
builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars) builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars)
if err != nil { if err != nil {
@@ -331,6 +331,7 @@ func (b *Builder) BuildPackage(
if ok { if ok {
builtDeps = append(builtDeps, &BuiltDep{ builtDeps = append(builtDeps, &BuiltDep{
Path: builtPkgPath, Path: builtPkgPath,
Name: vars.Name,
}) })
} else { } else {
remainingVars = append(remainingVars, vars) remainingVars = append(remainingVars, vars)
@@ -338,8 +339,12 @@ func (b *Builder) BuildPackage(
} }
if len(remainingVars) == 0 { if len(remainingVars) == 0 {
slog.Info(gotext.Get("Using cached package"), "name", basePkg)
return builtDeps, nil return builtDeps, nil
} }
// Обновляем varsOfPackages только теми пакетами, которые нужно собрать
varsOfPackages = remainingVars
} }
slog.Debug("ViewScript") slog.Debug("ViewScript")

View File

@@ -176,8 +176,13 @@ func getBasePkgInfo(vars *alrsh.Package, input interface {
OsInfoProvider OsInfoProvider
}, },
) *nfpm.Info { ) *nfpm.Info {
repo := input.Repository()
// Избегаем дублирования "alr-" префикса
if strings.HasPrefix(repo, "alr-") {
repo = repo[4:] // убираем "alr-" префикс
}
return &nfpm.Info{ return &nfpm.Info{
Name: fmt.Sprintf("%s+alr-%s", vars.Name, input.Repository()), Name: fmt.Sprintf("%s+alr-%s", vars.Name, repo),
Arch: cpu.Arch(), Arch: cpu.Arch(),
Version: vars.Version, Version: vars.Version,
Release: overrides.ReleasePlatformSpecific(vars.Release, input.OSRelease()), Release: overrides.ReleasePlatformSpecific(vars.Release, input.OSRelease()),

View File

@@ -22,11 +22,13 @@ package config
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"github.com/goccy/go-yaml" "github.com/goccy/go-yaml"
"github.com/knadh/koanf/providers/confmap" "github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/v2" "github.com/knadh/koanf/v2"
ktoml "github.com/knadh/koanf/parsers/toml/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" "gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
@@ -134,25 +136,12 @@ func (c *ALRConfig) ToYAML() (string, error) {
func (c *ALRConfig) migrateConfig() error { func (c *ALRConfig) migrateConfig() error {
// Проверяем, существует ли конфигурационный файл // Проверяем, существует ли конфигурационный файл
if _, err := os.Stat(constants.SystemConfigPath); os.IsNotExist(err) { if _, err := os.Stat(constants.SystemConfigPath); os.IsNotExist(err) {
// Если файла нет, но конфигурация уже загружена (из defaults или env), // Если файла нет, создаем полный конфигурационный файл с дефолтными значениями
// создаем файл с настройкой по умолчанию if err := c.createDefaultConfig(); err != nil {
needsCreation := false // Если не удается создать конфиг, это не критично - продолжаем работу
// но выводим предупреждение
// Проверяем, установлена ли переменная окружения ALR_UPDATESYSTEMONUPGRADE fmt.Fprintf(os.Stderr, "Предупреждение: не удалось создать конфигурационный файл %s: %v\n", constants.SystemConfigPath, err)
if os.Getenv("ALR_UPDATESYSTEMONUPGRADE") == "" { return nil
// Если переменная не установлена, проверяем наличие пакетов ALR
// чтобы определить, нужно ли включить эту опцию для обновления
needsCreation = true
}
if needsCreation {
// Устанавливаем значение false по умолчанию для новой опции
c.System.SetUpdateSystemOnUpgrade(false)
// Сохраняем конфигурацию
if err := c.System.Save(); err != nil {
// Если не удается сохранить - это не критично, продолжаем работу
return nil
}
} }
} else { } else {
// Если файл существует, проверяем, есть ли в нем новая опция // Если файл существует, проверяем, есть ли в нем новая опция
@@ -170,6 +159,100 @@ func (c *ALRConfig) migrateConfig() error {
return nil return nil
} }
func (c *ALRConfig) createDefaultConfig() error {
// Проверяем, запущен ли процесс от root
if os.Getuid() != 0 {
// Если не root, пытаемся запустить создание конфига с повышением привилегий
return c.createDefaultConfigWithPrivileges()
}
// Если уже root, создаем конфиг напрямую
return c.doCreateDefaultConfig()
}
func (c *ALRConfig) createDefaultConfigWithPrivileges() error {
// Если useRootCmd отключен, просто пытаемся создать без повышения привилегий
if !c.cfg.UseRootCmd {
return c.doCreateDefaultConfig()
}
// Определяем команду для повышения привилегий
rootCmd := c.cfg.RootCmd
if rootCmd == "" {
rootCmd = "sudo" // fallback
}
// Создаем временный файл с дефолтной конфигурацией
tmpFile, err := os.CreateTemp("", "alr-config-*.toml")
if err != nil {
return fmt.Errorf("не удалось создать временный файл: %w", err)
}
defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
// Генерируем дефолтную конфигурацию во временный файл
defaults := defaultConfigKoanf()
tempSystemConfig := &SystemConfig{k: defaults}
bytes, err := tempSystemConfig.k.Marshal(ktoml.Parser())
if err != nil {
return fmt.Errorf("не удалось сериализовать конфигурацию: %w", err)
}
if _, err := tmpFile.Write(bytes); err != nil {
return fmt.Errorf("не удалось записать во временный файл: %w", err)
}
tmpFile.Close()
// Используем команду повышения привилегий для создания директории и копирования файла
// Создаем директорию с правами
configDir := filepath.Dir(constants.SystemConfigPath)
mkdirCmd := exec.Command(rootCmd, "mkdir", "-p", configDir)
if err := mkdirCmd.Run(); err != nil {
return fmt.Errorf("не удалось создать директорию %s: %w", configDir, err)
}
// Копируем файл в нужное место
cpCmd := exec.Command(rootCmd, "cp", tmpFile.Name(), constants.SystemConfigPath)
if err := cpCmd.Run(); err != nil {
return fmt.Errorf("не удалось скопировать конфигурацию в %s: %w", constants.SystemConfigPath, err)
}
// Устанавливаем правильные права доступа
chmodCmd := exec.Command(rootCmd, "chmod", "644", constants.SystemConfigPath)
if err := chmodCmd.Run(); err != nil {
// Не критично, продолжаем
fmt.Fprintf(os.Stderr, "Предупреждение: не удалось установить права доступа для %s: %v\n", constants.SystemConfigPath, err)
}
return nil
}
func (c *ALRConfig) doCreateDefaultConfig() error {
// Проверяем, существует ли директория для конфига
configDir := filepath.Dir(constants.SystemConfigPath)
if _, err := os.Stat(configDir); os.IsNotExist(err) {
// Пытаемся создать директорию
if err := os.MkdirAll(configDir, 0755); err != nil {
return fmt.Errorf("не удалось создать директорию %s: %w", configDir, err)
}
}
// Загружаем дефолтную конфигурацию
defaults := defaultConfigKoanf()
// Копируем все дефолтные значения в системную конфигурацию
c.System.k = defaults
// Сохраняем конфигурацию в файл
if err := c.System.Save(); err != nil {
return fmt.Errorf("не удалось сохранить конфигурацию в %s: %w", constants.SystemConfigPath, err)
}
return nil
}
func (c *ALRConfig) RootCmd() string { return c.cfg.RootCmd } func (c *ALRConfig) RootCmd() string { return c.cfg.RootCmd }
func (c *ALRConfig) PagerStyle() string { return c.cfg.PagerStyle } func (c *ALRConfig) PagerStyle() string { return c.cfg.PagerStyle }
func (c *ALRConfig) AutoPull() bool { return c.cfg.AutoPull } func (c *ALRConfig) AutoPull() bool { return c.cfg.AutoPull }

View File

@@ -20,5 +20,6 @@ const (
SystemConfigPath = "/etc/alr/alr.toml" SystemConfigPath = "/etc/alr/alr.toml"
SystemCachePath = "/var/cache/alr" SystemCachePath = "/var/cache/alr"
TempDir = "/tmp/alr" TempDir = "/tmp/alr"
PrivilegedGroup = "wheel" // PrivilegedGroup - устарело, используйте GetPrivilegedGroup()
PrivilegedGroup = "wheel" // оставлено для обратной совместимости
) )

View File

@@ -62,11 +62,9 @@ func (d *Database) Connect() error {
dbDir := filepath.Dir(dsn) dbDir := filepath.Dir(dsn)
if _, err := os.Stat(dbDir); err != nil { if _, err := os.Stat(dbDir); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
// Директория не существует - пытаемся создать // Директория не существует - не пытаемся создать
if mkErr := os.MkdirAll(dbDir, 0775); mkErr != nil { // Пользователь должен использовать alr fix для создания системных каталогов
// Не смогли создать - вернём ошибку, пользователь должен использовать alr fix return fmt.Errorf("cache directory does not exist, please run 'sudo alr fix' to create it")
return fmt.Errorf("cache directory does not exist, please run 'alr fix' to create it: %w", mkErr)
}
} else { } else {
return fmt.Errorf("failed to check database directory: %w", err) return fmt.Errorf("failed to check database directory: %w", err)
} }

View File

@@ -140,16 +140,19 @@ func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) {
} }
func (a *APT) IsInstalled(pkg string) (bool, error) { func (a *APT) IsInstalled(pkg string) (bool, error) {
cmd := exec.Command("dpkg-query", "-l", pkg) cmd := exec.Command("dpkg-query", "-f", "${Status}", "-W", pkg)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok { if exitErr, ok := err.(*exec.ExitError); ok {
// Exit code 1 means the package is not installed // Код выхода 1 означает что пакет не найден
if exitErr.ExitCode() == 1 { if exitErr.ExitCode() == 1 {
return false, nil return false, nil
} }
} }
return false, fmt.Errorf("apt: isinstalled: %w, output: %s", err, output) return false, fmt.Errorf("apt: isinstalled: %w, output: %s", err, output)
} }
return true, nil
status := strings.TrimSpace(string(output))
// Проверяем что пакет действительно установлен (статус должен содержать "install ok installed")
return strings.Contains(status, "install ok installed"), nil
} }

View File

@@ -19,14 +19,12 @@ package utils
import ( import (
"os" "os"
"os/exec" "os/exec"
"os/user"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"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/constants"
) )
// IsNotRoot проверяет, что текущий пользователь не является root // IsNotRoot проверяет, что текущий пользователь не является root
@@ -34,39 +32,10 @@ func IsNotRoot() bool {
return os.Getuid() != 0 return os.Getuid() != 0
} }
// EnuseIsPrivilegedGroupMember проверяет, что пользователь является членом привилегированной группы (wheel) // EnuseIsPrivilegedGroupMember проверяет, что пользователь является членом привилегированной группы (wheel/sudo)
// DEPRECATED: используйте CheckUserPrivileges() из utils.go
func EnuseIsPrivilegedGroupMember() error { func EnuseIsPrivilegedGroupMember() error {
// В CI пропускаем проверку группы wheel return CheckUserPrivileges()
if os.Getenv("CI") == "true" {
return nil
}
// Если пользователь root, пропускаем проверку
if os.Geteuid() == 0 {
return nil
}
currentUser, err := user.Current()
if err != nil {
return err
}
group, err := user.LookupGroup(constants.PrivilegedGroup)
if err != nil {
return err
}
groups, err := currentUser.GroupIds()
if err != nil {
return err
}
for _, gid := range groups {
if gid == group.Gid {
return nil
}
}
return cliutils.FormatCliExit(gotext.Get("You need to be a %s member to perform this action", constants.PrivilegedGroup), nil)
} }
func RootNeededAction(f cli.ActionFunc) cli.ActionFunc { func RootNeededAction(f cli.ActionFunc) cli.ActionFunc {

View File

@@ -0,0 +1,76 @@
// 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/>.
package utils
import (
"context"
"os/user"
"sync"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
var (
privilegedGroupCache string
privilegedGroupOnce sync.Once
)
// GetPrivilegedGroup определяет правильную привилегированную группу для текущего дистрибутива.
// Дистрибутивы на базе Debian/Ubuntu используют группу "sudo", остальные - "wheel".
func GetPrivilegedGroup() string {
privilegedGroupOnce.Do(func() {
privilegedGroupCache = detectPrivilegedGroup()
})
return privilegedGroupCache
}
func detectPrivilegedGroup() string {
// Попробуем определить дистрибутив
ctx := context.Background()
osInfo, err := distro.ParseOSRelease(ctx)
if err != nil {
// Если не можем определить дистрибутив, проверяем какие группы существуют
return detectGroupByAvailability()
}
// Проверяем ID и семейство дистрибутива
// Debian и его производные (Ubuntu, Mint, PopOS и т.д.) используют sudo
if osInfo.ID == "debian" || osInfo.ID == "ubuntu" {
return "sudo"
}
// Проверяем семейство дистрибутива через ID_LIKE
for _, like := range osInfo.Like {
if like == "debian" || like == "ubuntu" {
return "sudo"
}
}
// Для остальных дистрибутивов (Fedora, RHEL, Arch, openSUSE, ALT Linux) используется wheel
return "wheel"
}
// detectGroupByAvailability проверяет существование групп в системе
func detectGroupByAvailability() string {
// Сначала проверяем группу sudo (более распространена)
if _, err := user.LookupGroup("sudo"); err == nil {
return "sudo"
}
// Если sudo не найдена, возвращаем wheel
return "wheel"
}

View File

@@ -17,8 +17,10 @@
package utils package utils
import ( import (
"fmt"
"os" "os"
"os/exec" "os/exec"
"os/user"
"strings" "strings"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@@ -28,23 +30,23 @@ 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 // EnsureTempDirWithRootOwner создает каталог в /tmp/alr или /var/cache/alr с правами для привилегированной группы
// Все каталоги в /tmp/alr принадлежат root:wheel с правами 775 // Все каталоги в /tmp/alr и /var/cache/alr принадлежат root:привилегированная_группа с правами 2775
// Для других каталогов использует стандартные права // Для других каталогов использует стандартные права
func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error { func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error {
if strings.HasPrefix(path, "/tmp/alr") { needsElevation := strings.HasPrefix(path, "/tmp/alr") || strings.HasPrefix(path, "/var/cache/alr")
// Сначала создаем директорию обычным способом
err := os.MkdirAll(path, mode)
if err != nil {
return err
}
if needsElevation {
// В CI или если мы уже root, не нужно использовать sudo // В CI или если мы уже root, не нужно использовать sudo
isRoot := os.Geteuid() == 0 isRoot := os.Geteuid() == 0
isCI := os.Getenv("CI") == "true" isCI := os.Getenv("CI") == "true"
// В CI создаем директории с обычными правами // В CI создаем директории с обычными правами
if isCI { if isCI {
err := os.MkdirAll(path, mode)
if err != nil {
return err
}
// В CI не используем группу wheel и не меняем права // В CI не используем группу wheel и не меняем права
// Устанавливаем базовые права 777 для временных каталогов // Устанавливаем базовые права 777 для временных каталогов
chmodCmd := exec.Command("chmod", "777", path) chmodCmd := exec.Command("chmod", "777", path)
@@ -52,36 +54,48 @@ func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error {
return nil return nil
} }
// Для обычной работы устанавливаем права и группу wheel // Для обычной работы устанавливаем права и привилегированную группу
permissions := "2775" permissions := "2775"
group := "wheel" group := GetPrivilegedGroup()
var chmodCmd, chownCmd *exec.Cmd var mkdirCmd, chmodCmd, chownCmd *exec.Cmd
if isRoot { if isRoot {
// Выполняем команды напрямую без sudo // Выполняем команды напрямую без sudo
mkdirCmd = exec.Command("mkdir", "-p", path)
chmodCmd = exec.Command("chmod", permissions, path) chmodCmd = exec.Command("chmod", permissions, path)
chownCmd = exec.Command("chown", "root:"+group, path) chownCmd = exec.Command("chown", "root:"+group, path)
} else { } else {
// Используем sudo для обычных пользователей // Используем sudo для всех операций с привилегированными каталогами
mkdirCmd = exec.Command("sudo", "mkdir", "-p", path)
chmodCmd = exec.Command("sudo", "chmod", permissions, path) chmodCmd = exec.Command("sudo", "chmod", permissions, path)
chownCmd = exec.Command("sudo", "chown", "root:"+group, path) chownCmd = exec.Command("sudo", "chown", "root:"+group, path)
} }
// Устанавливаем права с setgid битом // Создаем директорию через sudo если нужно
err = chmodCmd.Run() err := mkdirCmd.Run()
if err != nil { if err != nil {
// Для root игнорируем ошибки, если группа wheel не существует // Игнорируем ошибку если директория уже существует
if !isRoot { if !isRoot {
return err // Проверяем существует ли директория
if _, statErr := os.Stat(path); statErr != nil {
return fmt.Errorf("не удалось создать директорию %s: %w", path, err)
}
} }
} }
// Устанавливаем владельца root:wheel // Устанавливаем права с setgid битом для наследования группы
err = chmodCmd.Run()
if err != nil {
if !isRoot {
return fmt.Errorf("не удалось установить права на %s: %w", path, err)
}
}
// Устанавливаем владельца root:группа
err = chownCmd.Run() err = chownCmd.Run()
if err != nil { if err != nil {
// Для root игнорируем ошибки, если группа wheel не существует
if !isRoot { if !isRoot {
return err return fmt.Errorf("не удалось установить владельца на %s: %w", path, err)
} }
} }
@@ -91,3 +105,60 @@ func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error {
// Для остальных каталогов обычное создание // Для остальных каталогов обычное создание
return os.MkdirAll(path, mode) return os.MkdirAll(path, mode)
} }
// IsUserInGroup проверяет, состоит ли пользователь в указанной группе
func IsUserInGroup(username, groupname string) bool {
u, err := user.Lookup(username)
if err != nil {
return false
}
groups, err := u.GroupIds()
if err != nil {
return false
}
targetGroup, err := user.LookupGroup(groupname)
if err != nil {
return false
}
for _, gid := range groups {
if gid == targetGroup.Gid {
return true
}
}
return false
}
// CheckUserPrivileges проверяет, что пользователь имеет необходимые привилегии для работы с ALR
// Пользователь должен быть root или состоять в группе wheel/sudo
func CheckUserPrivileges() error {
// Если пользователь root - все в порядке
if os.Geteuid() == 0 {
return nil
}
// В CI не проверяем привилегии
if os.Getenv("CI") == "true" {
return nil
}
currentUser, err := user.Current()
if err != nil {
return fmt.Errorf("не удалось получить информацию о текущем пользователе: %w", err)
}
privilegedGroup := GetPrivilegedGroup()
// Проверяем членство в привилегированной группе
if !IsUserInGroup(currentUser.Username, privilegedGroup) {
return fmt.Errorf("пользователь %s не имеет необходимых привилегий для работы с ALR.\n"+
"Для работы с ALR необходимо быть пользователем root или состоять в группе %s.\n"+
"Выполните команду: sudo usermod -a -G %s %s\n"+
"Затем перезайдите в систему или выполните: newgrp %s",
currentUser.Username, privilegedGroup, privilegedGroup, currentUser.Username, privilegedGroup)
}
return nil
}

View File

@@ -21,6 +21,7 @@ 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 {
@@ -29,6 +30,9 @@ 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.CheckUserPrivileges(); err != nil {
return err
}
ctx := c.Context ctx := c.Context

View File

@@ -167,13 +167,13 @@ if [ -z "$noPkgMgr" ]; then
info "Получен список файлов релиза" info "Получен список файлов релиза"
if [ "$pkgMgr" == "pacman" ]; then if [ "$pkgMgr" == "pacman" ]; then
latestFile=$(echo "$fileList" | grep -E "alr-bin.*\.pkg\.tar\.zst" | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E "alr-bin.*-(${arch}|any)\.pkg\.tar\.zst" | sort -V | tail -n 1)
elif [ "$pkgMgr" == "apt" ]; then elif [ "$pkgMgr" == "apt" ]; then
latestFile=$(echo "$fileList" | grep -E "alr-bin.*\.${debArch}\.deb" | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E "alr-bin.*_(${debArch}|all)\.deb" | sort -V | tail -n 1)
elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then
latestFile=$(echo "$fileList" | grep -E "alr-bin.*\.${rpmArch}\.rpm" | grep -v 'alt[0-9]*' | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E "alr-bin.*\.(${rpmArch}|noarch)\.rpm" | grep -v 'alt[0-9]*' | sort -V | tail -n 1)
elif [ "$pkgMgr" == "apt-get" ]; then elif [ "$pkgMgr" == "apt-get" ]; then
latestFile=$(echo "$fileList" | grep -E "alr-bin.*-alt[0-9]+\.${rpmArch}\.rpm" | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E "alr-bin.*-alt[0-9]+\.(${rpmArch}|noarch)\.rpm" | sort -V | tail -n 1)
elif [ "$pkgMgr" == "apk" ]; then elif [ "$pkgMgr" == "apk" ]; then
latestFile=$(echo "$fileList" | grep -E "alr-bin.*\.apk" | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E "alr-bin.*\.apk" | sort -V | tail -n 1)
else else