2025-01-18 19:30:02 +03:00
|
|
|
|
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
|
2025-05-07 21:08:12 +03:00
|
|
|
|
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
|
2025-01-18 19:30:02 +03:00
|
|
|
|
//
|
|
|
|
|
|
// ALR - Any Linux Repository
|
2025-05-07 21:08:12 +03:00
|
|
|
|
// Copyright (C) 2025 The ALR Authors
|
2025-01-18 19:30:02 +03:00
|
|
|
|
//
|
|
|
|
|
|
// 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/>.
|
2024-01-22 13:36:06 +03:00
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-06-14 15:05:18 +03:00
|
|
|
|
"io/fs"
|
2025-01-22 16:37:16 +03:00
|
|
|
|
"log/slog"
|
2024-01-22 13:36:06 +03:00
|
|
|
|
"os"
|
2025-08-26 22:09:28 +03:00
|
|
|
|
"os/exec"
|
2025-04-15 21:41:21 +03:00
|
|
|
|
"path/filepath"
|
2024-01-22 13:36:06 +03:00
|
|
|
|
|
2025-01-22 16:37:16 +03:00
|
|
|
|
"github.com/leonelquinteros/gotext"
|
2024-01-22 13:36:06 +03:00
|
|
|
|
"github.com/urfave/cli/v2"
|
2025-01-18 19:30:02 +03:00
|
|
|
|
|
2025-04-15 21:41:21 +03:00
|
|
|
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
|
|
|
|
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
|
|
|
|
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
2024-01-22 13:36:06 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-08-27 01:45:54 +03:00
|
|
|
|
// execWithPrivileges выполняет команду напрямую если root или CI, иначе через sudo
|
|
|
|
|
|
func execWithPrivileges(name string, args ...string) *exec.Cmd {
|
|
|
|
|
|
isRoot := os.Geteuid() == 0
|
|
|
|
|
|
isCI := os.Getenv("CI") == "true"
|
|
|
|
|
|
|
|
|
|
|
|
if !isRoot && !isCI {
|
|
|
|
|
|
// Если не root и не в CI, используем sudo
|
|
|
|
|
|
allArgs := append([]string{name}, args...)
|
|
|
|
|
|
return exec.Command("sudo", allArgs...)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Если root или в CI, запускаем напрямую
|
|
|
|
|
|
return exec.Command(name, args...)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-22 17:16:15 +03:00
|
|
|
|
func FixCmd() *cli.Command {
|
|
|
|
|
|
return &cli.Command{
|
|
|
|
|
|
Name: "fix",
|
|
|
|
|
|
Usage: gotext.Get("Attempt to fix problems with ALR"),
|
|
|
|
|
|
Action: func(c *cli.Context) error {
|
2025-08-26 22:09:28 +03:00
|
|
|
|
// Команда выполняется от текущего пользователя
|
|
|
|
|
|
// При необходимости будет запрошен sudo для удаления файлов root
|
2025-04-15 21:41:21 +03:00
|
|
|
|
|
2025-01-22 17:16:15 +03:00
|
|
|
|
ctx := c.Context
|
2025-04-15 21:41:21 +03:00
|
|
|
|
|
|
|
|
|
|
deps, err := appbuilder.
|
|
|
|
|
|
New(ctx).
|
|
|
|
|
|
WithConfig().
|
|
|
|
|
|
Build()
|
2025-03-22 12:58:10 +03:00
|
|
|
|
if err != nil {
|
2025-04-15 21:41:21 +03:00
|
|
|
|
return cli.Exit(err, 1)
|
2025-03-22 12:58:10 +03:00
|
|
|
|
}
|
2025-04-15 21:41:21 +03:00
|
|
|
|
defer deps.Defer()
|
|
|
|
|
|
|
|
|
|
|
|
cfg := deps.Cfg
|
2025-03-22 12:58:10 +03:00
|
|
|
|
|
|
|
|
|
|
paths := cfg.GetPaths()
|
2024-01-22 13:36:06 +03:00
|
|
|
|
|
2025-08-26 22:09:28 +03:00
|
|
|
|
slog.Info(gotext.Get("Clearing cache and temporary directories"))
|
2024-01-22 13:36:06 +03:00
|
|
|
|
|
2025-08-26 22:09:28 +03:00
|
|
|
|
// Проверяем, существует ли директория кэша
|
2025-04-15 21:41:21 +03:00
|
|
|
|
dir, err := os.Open(paths.CacheDir)
|
2025-01-22 17:16:15 +03:00
|
|
|
|
if err != nil {
|
2025-08-26 22:09:28 +03:00
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
|
|
// Директория не существует, просто создадим её позже
|
|
|
|
|
|
slog.Info(gotext.Get("Cache directory does not exist, will create it"))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
defer dir.Close()
|
|
|
|
|
|
|
|
|
|
|
|
entries, err := dir.Readdirnames(-1)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
|
|
fullPath := filepath.Join(paths.CacheDir, entry)
|
|
|
|
|
|
|
|
|
|
|
|
// Пробуем сделать файлы доступными для записи
|
|
|
|
|
|
if err := makeWritableRecursive(fullPath); err != nil {
|
|
|
|
|
|
slog.Debug("Failed to make path writable", "path", fullPath, "error", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Пробуем удалить
|
|
|
|
|
|
err = os.RemoveAll(fullPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// Если не получилось удалить, пробуем через sudo
|
|
|
|
|
|
slog.Warn(gotext.Get("Unable to remove cache item (%s) as current user, trying with sudo", entry))
|
|
|
|
|
|
|
2025-08-27 01:45:54 +03:00
|
|
|
|
sudoCmd := execWithPrivileges("rm", "-rf", fullPath)
|
2025-08-26 22:09:28 +03:00
|
|
|
|
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"))
|
2025-08-27 01:45:54 +03:00
|
|
|
|
sudoCmd := execWithPrivileges("rm", "-rf", tmpDir)
|
2025-08-26 22:09:28 +03:00
|
|
|
|
if sudoErr := sudoCmd.Run(); sudoErr != nil {
|
|
|
|
|
|
slog.Error(gotext.Get("Unable to remove temporary directory"), "error", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-01-22 17:16:15 +03:00
|
|
|
|
}
|
2024-01-22 13:36:06 +03:00
|
|
|
|
|
2025-09-21 15:31:51 +03:00
|
|
|
|
// Создаем базовый каталог /tmp/alr с владельцем root:wheel и правами 2775
|
|
|
|
|
|
err = utils.EnsureTempDirWithRootOwner(tmpDir, 0o2775)
|
2025-01-22 17:16:15 +03:00
|
|
|
|
if err != nil {
|
2025-08-26 22:09:28 +03:00
|
|
|
|
slog.Warn(gotext.Get("Unable to create temporary directory"), "error", err)
|
2025-01-22 17:16:15 +03:00
|
|
|
|
}
|
2024-01-22 13:36:06 +03:00
|
|
|
|
|
2025-08-26 22:09:28 +03:00
|
|
|
|
// Создаем каталог dl с правами для группы wheel
|
|
|
|
|
|
dlDir := filepath.Join(tmpDir, "dl")
|
2025-09-21 15:31:51 +03:00
|
|
|
|
err = utils.EnsureTempDirWithRootOwner(dlDir, 0o2775)
|
2025-08-26 22:09:28 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
slog.Warn(gotext.Get("Unable to create download directory"), "error", err)
|
|
|
|
|
|
}
|
2025-06-14 15:05:18 +03:00
|
|
|
|
|
2025-08-26 22:09:28 +03:00
|
|
|
|
// Создаем каталог pkgs с правами для группы wheel
|
|
|
|
|
|
pkgsDir := filepath.Join(tmpDir, "pkgs")
|
2025-09-21 15:31:51 +03:00
|
|
|
|
err = utils.EnsureTempDirWithRootOwner(pkgsDir, 0o2775)
|
2025-08-26 22:09:28 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
slog.Warn(gotext.Get("Unable to create packages directory"), "error", err)
|
|
|
|
|
|
}
|
2025-06-14 15:05:18 +03:00
|
|
|
|
|
2025-08-26 22:09:28 +03:00
|
|
|
|
// Исправляем права на все существующие файлы в /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 {
|
2025-09-21 15:31:51 +03:00
|
|
|
|
group := utils.GetPrivilegedGroup()
|
|
|
|
|
|
fixCmd := execWithPrivileges("chown", "-R", "root:"+group, tmpDir)
|
2025-08-26 22:09:28 +03:00
|
|
|
|
if fixErr := fixCmd.Run(); fixErr != nil {
|
|
|
|
|
|
slog.Warn(gotext.Get("Unable to fix file ownership"), "error", fixErr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-27 01:45:54 +03:00
|
|
|
|
fixCmd = execWithPrivileges("chmod", "-R", "2775", tmpDir)
|
2025-08-26 22:09:28 +03:00
|
|
|
|
if fixErr := fixCmd.Run(); fixErr != nil {
|
|
|
|
|
|
slog.Warn(gotext.Get("Unable to fix file permissions"), "error", fixErr)
|
|
|
|
|
|
}
|
2025-04-15 21:41:21 +03:00
|
|
|
|
}
|
2025-03-22 12:58:10 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-15 21:41:21 +03:00
|
|
|
|
slog.Info(gotext.Get("Rebuilding cache"))
|
|
|
|
|
|
|
2025-09-21 15:31:51 +03:00
|
|
|
|
// Создаем директорию кэша с правильными правами
|
|
|
|
|
|
slog.Info(gotext.Get("Creating cache directory"))
|
|
|
|
|
|
err = utils.EnsureTempDirWithRootOwner(paths.CacheDir, 0o2775)
|
2025-02-22 09:44:59 +03:00
|
|
|
|
if err != nil {
|
2025-09-21 15:31:51 +03:00
|
|
|
|
return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err)
|
2025-02-22 09:44:59 +03:00
|
|
|
|
}
|
2025-04-15 21:41:21 +03:00
|
|
|
|
|
|
|
|
|
|
deps, err = appbuilder.
|
|
|
|
|
|
New(ctx).
|
|
|
|
|
|
WithConfig().
|
|
|
|
|
|
WithDB().
|
|
|
|
|
|
WithReposForcePull().
|
|
|
|
|
|
Build()
|
2025-01-22 17:16:15 +03:00
|
|
|
|
if err != nil {
|
2025-04-15 21:41:21 +03:00
|
|
|
|
return cli.Exit(err, 1)
|
2025-01-22 17:16:15 +03:00
|
|
|
|
}
|
2025-04-15 21:41:21 +03:00
|
|
|
|
defer deps.Defer()
|
2024-01-22 13:36:06 +03:00
|
|
|
|
|
2025-01-22 17:16:15 +03:00
|
|
|
|
slog.Info(gotext.Get("Done"))
|
2024-01-22 13:36:06 +03:00
|
|
|
|
|
2025-01-22 17:16:15 +03:00
|
|
|
|
return nil
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
2024-01-22 13:36:06 +03:00
|
|
|
|
}
|
2025-06-14 15:05:18 +03:00
|
|
|
|
|
|
|
|
|
|
func makeWritableRecursive(path string) error {
|
|
|
|
|
|
return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
info, err := d.Info()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
newMode := info.Mode() | 0o200
|
|
|
|
|
|
if d.IsDir() {
|
|
|
|
|
|
newMode |= 0o100
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return os.Chmod(path, newMode)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|