diff --git a/.gitignore b/.gitignore index 0a4426d..15df47d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ CLAUDE.md commit_msg.txt /scripts/.claude/settings.local.json /ALR +.claude/settings.local.json diff --git a/internal/db/db.go b/internal/db/db.go index 945a167..b296089 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -31,6 +31,7 @@ import ( "xorm.io/xorm" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config" + "gitea.plemya-x.ru/Plemya-x/ALR/internal/fsutils" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" ) @@ -57,19 +58,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) { - // Директория не существует - не пытаемся создать - // Пользователь должен использовать alr fix для создания системных каталогов - return fmt.Errorf("cache directory does not exist, please run 'sudo alr fix' to create it") + // Директория не существует - создаем автоматически + slog.Info(gotext.Get("Cache directory does not exist, creating it")) + if err := fsutils.EnsureTempDirWithRootOwner(dbDir, 0o2775); err != nil { + return fmt.Errorf("failed to create cache directory: %w", err) + } } 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) diff --git a/internal/fsutils/dirs.go b/internal/fsutils/dirs.go new file mode 100644 index 0000000..b92154c --- /dev/null +++ b/internal/fsutils/dirs.go @@ -0,0 +1,100 @@ +// 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 . + +package fsutils + +import ( + "fmt" + "os" + "os/exec" + "strings" +) + +// EnsureTempDirWithRootOwner создает каталог в /tmp/alr или /var/cache/alr с правами для привилегированной группы +// Все каталоги в /tmp/alr и /var/cache/alr принадлежат root:привилегированная_группа с правами 2775 +// Для других каталогов использует стандартные права +func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error { + needsElevation := strings.HasPrefix(path, "/tmp/alr") || strings.HasPrefix(path, "/var/cache/alr") + + if needsElevation { + // В CI или если мы уже root, не нужно использовать sudo + isRoot := os.Geteuid() == 0 + isCI := os.Getenv("CI") == "true" + + // В CI создаем директории с обычными правами + if isCI { + err := os.MkdirAll(path, mode) + if err != nil { + return err + } + // В CI не используем группу wheel и не меняем права + // Устанавливаем базовые права 777 для временных каталогов + chmodCmd := exec.Command("chmod", "777", path) + chmodCmd.Run() // Игнорируем ошибки + return nil + } + + // Для обычной работы устанавливаем права и привилегированную группу + permissions := "2775" + group := GetPrivilegedGroup() + + var mkdirCmd, chmodCmd, chownCmd *exec.Cmd + if isRoot { + // Выполняем команды напрямую без sudo + mkdirCmd = exec.Command("mkdir", "-p", path) + chmodCmd = exec.Command("chmod", permissions, path) + chownCmd = exec.Command("chown", "root:"+group, path) + } else { + // Используем sudo для всех операций с привилегированными каталогами + mkdirCmd = exec.Command("sudo", "mkdir", "-p", path) + chmodCmd = exec.Command("sudo", "chmod", permissions, path) + chownCmd = exec.Command("sudo", "chown", "root:"+group, path) + } + + // Создаем директорию через sudo если нужно + err := mkdirCmd.Run() + if err != nil { + // Игнорируем ошибку если директория уже существует + if !isRoot { + // Проверяем существует ли директория + if _, statErr := os.Stat(path); statErr != nil { + return fmt.Errorf("не удалось создать директорию %s: %w", path, err) + } + } + } + + // Устанавливаем права с setgid битом для наследования группы + err = chmodCmd.Run() + if err != nil { + if !isRoot { + return fmt.Errorf("не удалось установить права на %s: %w", path, err) + } + } + + // Устанавливаем владельца root:группа + err = chownCmd.Run() + if err != nil { + if !isRoot { + return fmt.Errorf("не удалось установить владельца на %s: %w", path, err) + } + } + + return nil + } + + // Для остальных каталогов обычное создание + return os.MkdirAll(path, mode) +} diff --git a/internal/utils/privileged_group.go b/internal/fsutils/privileged_group.go similarity index 99% rename from internal/utils/privileged_group.go rename to internal/fsutils/privileged_group.go index 91916fd..d98ebbe 100644 --- a/internal/utils/privileged_group.go +++ b/internal/fsutils/privileged_group.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package utils +package fsutils import ( "context" diff --git a/internal/utils/utils.go b/internal/utils/utils.go index f3cf019..40fa4a1 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -19,10 +19,9 @@ package utils import ( "fmt" "os" - "os/exec" "os/user" - "strings" + "gitea.plemya-x.ru/Plemya-x/ALR/internal/fsutils" "golang.org/x/sys/unix" ) @@ -31,79 +30,15 @@ func NoNewPrivs() error { } // EnsureTempDirWithRootOwner создает каталог в /tmp/alr или /var/cache/alr с правами для привилегированной группы -// Все каталоги в /tmp/alr и /var/cache/alr принадлежат root:привилегированная_группа с правами 2775 -// Для других каталогов использует стандартные права +// Обёртка для обратной совместимости, делегирует вызов в fsutils func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error { - needsElevation := strings.HasPrefix(path, "/tmp/alr") || strings.HasPrefix(path, "/var/cache/alr") + return fsutils.EnsureTempDirWithRootOwner(path, mode) +} - if needsElevation { - // В CI или если мы уже root, не нужно использовать sudo - isRoot := os.Geteuid() == 0 - isCI := os.Getenv("CI") == "true" - - // В CI создаем директории с обычными правами - if isCI { - err := os.MkdirAll(path, mode) - if err != nil { - return err - } - // В CI не используем группу wheel и не меняем права - // Устанавливаем базовые права 777 для временных каталогов - chmodCmd := exec.Command("chmod", "777", path) - chmodCmd.Run() // Игнорируем ошибки - return nil - } - - // Для обычной работы устанавливаем права и привилегированную группу - permissions := "2775" - group := GetPrivilegedGroup() - - var mkdirCmd, chmodCmd, chownCmd *exec.Cmd - if isRoot { - // Выполняем команды напрямую без sudo - mkdirCmd = exec.Command("mkdir", "-p", path) - chmodCmd = exec.Command("chmod", permissions, path) - chownCmd = exec.Command("chown", "root:"+group, path) - } else { - // Используем sudo для всех операций с привилегированными каталогами - mkdirCmd = exec.Command("sudo", "mkdir", "-p", path) - chmodCmd = exec.Command("sudo", "chmod", permissions, path) - chownCmd = exec.Command("sudo", "chown", "root:"+group, path) - } - - // Создаем директорию через sudo если нужно - err := mkdirCmd.Run() - if err != nil { - // Игнорируем ошибку если директория уже существует - if !isRoot { - // Проверяем существует ли директория - if _, statErr := os.Stat(path); statErr != nil { - return fmt.Errorf("не удалось создать директорию %s: %w", path, err) - } - } - } - - // Устанавливаем права с setgid битом для наследования группы - err = chmodCmd.Run() - if err != nil { - if !isRoot { - return fmt.Errorf("не удалось установить права на %s: %w", path, err) - } - } - - // Устанавливаем владельца root:группа - err = chownCmd.Run() - if err != nil { - if !isRoot { - return fmt.Errorf("не удалось установить владельца на %s: %w", path, err) - } - } - - return nil - } - - // Для остальных каталогов обычное создание - return os.MkdirAll(path, mode) +// GetPrivilegedGroup возвращает привилегированную группу для текущей системы +// Обёртка для обратной совместимости, делегирует вызов в fsutils +func GetPrivilegedGroup() string { + return fsutils.GetPrivilegedGroup() } // IsUserInGroup проверяет, состоит ли пользователь в указанной группе @@ -149,7 +84,7 @@ func CheckUserPrivileges() error { return fmt.Errorf("не удалось получить информацию о текущем пользователе: %w", err) } - privilegedGroup := GetPrivilegedGroup() + privilegedGroup := fsutils.GetPrivilegedGroup() // Проверяем членство в привилегированной группе if !IsUserInGroup(currentUser.Username, privilegedGroup) {