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) {