18 Commits

Author SHA1 Message Date
d7e910c06c Оптимизация сборки зависимостей и исправление кеширования
All checks were successful
Create Release / changelog (push) Successful in 2m26s
- Добавлено полное разрешение дерева зависимостей перед сборкой
- Общие зависимости теперь собираются только один раз
- Исправлена работа кеша для подпакетов
- Исправлена обработка системных зависимостей
2025-12-08 21:58:41 +00:00
6529094fa7 Добавлен go-generate hook в pre-commit и лицензионные заголовки в автогенерируемые файлы
Some checks failed
Pre-commit / pre-commit (push) Failing after 3m21s
Изменения:
- Добавлен hook go-generate перед update-license в .pre-commit-config.yaml
- Добавлены лицензионные заголовки в pkg/alrsh/package_gen.go
2025-12-07 11:19:42 +03:00
c2d48c1a13 Исправлена проблема дублирования обновлений пакетов с подпакетами
Some checks failed
Pre-commit / pre-commit (push) Failing after 8m9s
Create Release / changelog (push) Successful in 3m30s
Изменения:
- Заменён вызов InstallALRPackages на InstallPkgs в upgrade.go
- Переименована функция mapUptatesInfoToPackages в mapUpdatesToPackageNames
- Добавлена дедупликация подпакетов по полному имени (package+repo)
- Теперь возвращаются строки с именами пакетов вместо объектов Package
2025-12-06 13:08:13 +03:00
72cdfcaa4b Замена vercmp и оптимизация сборки зависимостей с полной русификацией интерфейса
All checks were successful
Pre-commit / pre-commit (push) Successful in 6m13s
Create Release / changelog (push) Successful in 2m58s
- Заменен vercmp с go.elara.ws/vercmp на gitea.plemya-x.ru/xpamych/vercmp v0.0.1
- Добавлена функция FilterPackagesByVersion для проверки версий установленных
  пакетов перед пересборкой зависимостей (учитывает version-release и epoch)
- Исправлена инициализация переводов в плагинах: добавлены вызовы translations.Setup()
  во всех plugin subcommands (_internal-safe-script-executor, _internal-installer,
  _internal-repos)
- Добавлен GetSubcommandHelpTemplate для корректного отображения справки команд
  с подкомандами на русском языке
- Добавлены кастомные help команды для config, repo, helper и mirror
- Добавлены русские переводы для всех пользовательских сообщений:
  * Сообщения о создании пакетов (Creating package file, Packaging with nfpm и др.)
  * Сообщения команды fix (Clearing cache, Fixing permissions и др.)
  * Сообщения обновления (Updating system packages, System packages updated)
  * Сообщения о версиях пакетов (Package is installed with older/newer version)
  * Заголовки справки (NAME, USAGE, COMMANDS, OPTIONS)
  * Справочные сообщения (Shows a list of commands or help for one command)
- Оптимизирован assets/logo.png (уменьшен с 37KB до 17KB)
2025-11-29 19:32:13 +03:00
c9c8397856 Исправлена проблема, когда при первом запуске ALR требовалось вручную
All checks were successful
Pre-commit / pre-commit (push) Successful in 6m30s
Create Release / changelog (push) Successful in 3m31s
выполнять 'sudo alr fix' для создания необходимых директорий. Теперь
  директории /var/cache/alr и /tmp/alr создаются автоматически при первом
  использовании с правильными правами доступа.
2025-11-23 15:16:22 +03:00
107075e8ef Исправлен dlcache_prod
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m46s
2025-10-12 19:11:15 +03:00
41e3d8119f Добавлены files-find: systemd, systemd-user, license
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m33s
Create Release / changelog (push) Successful in 3m12s
2025-09-25 22:10:47 +03:00
cf804ec66b Исправлена проблема с перемещением готового пакета из временной дирректории сборки (в случае зависимости)
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m12s
Create Release / changelog (push) Successful in 3m6s
2025-09-21 17:50:31 +03:00
6773d51caf Добавление функций обработки files
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m8s
Create Release / changelog (push) Successful in 3m5s
2025-09-21 16:42:04 +03:00
4a616f2137 Исправление функционала создания дирректорий для работы ALR
All checks were successful
Pre-commit / pre-commit (push) Successful in 5m23s
Create Release / changelog (push) Successful in 3m3s
2025-09-21 16:21:23 +03:00
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
46 changed files with 2164 additions and 524 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

5
.gitignore vendored
View File

@@ -11,4 +11,7 @@
e2e-tests/alr e2e-tests/alr
CLAUDE.md CLAUDE.md
commit_msg.txt commit_msg.txt
/scripts/.claude/settings.local.json
/ALR
.claude/settings.local.json

View File

@@ -29,6 +29,12 @@ repos:
language: system language: system
pass_filenames: false pass_filenames: false
- id: go-generate
name: Run go generate
entry: bash -c 'go generate ./...'
language: system
pass_filenames: false
- id: update-license - id: update-license
name: Update license name: Update license
entry: make update-license entry: make update-license

View File

@@ -12,7 +12,7 @@
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text> <text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text>
<text x="37" y="14">ru translate</text> <text x="37" y="14">ru translate</text>
<text x="100" y="15" fill="#010101" fill-opacity=".3">100.00%</text> <text x="100" y="15" fill="#010101" fill-opacity=".3">97.00%</text>
<text x="100" y="14">100.00%</text> <text x="100" y="14">97.00%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 942 B

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 17 KiB

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
} }
@@ -197,6 +197,13 @@ func BuildCmd() *cli.Command {
for _, pkg := range res { for _, pkg := range res {
name := filepath.Base(pkg.Path) name := filepath.Base(pkg.Path)
// Проверяем, существует ли файл перед перемещением
if _, err := os.Stat(pkg.Path); os.IsNotExist(err) {
slog.Info(gotext.Get("Package file already moved or removed, skipping"), "path", pkg.Path)
continue
}
err = osutils.Move(pkg.Path, filepath.Join(wd, name)) err = osutils.Move(pkg.Path, filepath.Join(wd, name))
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err) return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err)

View File

@@ -38,6 +38,24 @@ func ConfigCmd() *cli.Command {
ShowCmd(), ShowCmd(),
SetConfig(), SetConfig(),
GetConfig(), GetConfig(),
ConfigHelpCmd(),
},
}
}
func ConfigHelpCmd() *cli.Command {
return &cli.Command{
Name: "help",
Aliases: []string{"h"},
Usage: gotext.Get("Shows a list of commands or help for one command"),
ArgsUsage: "[command]",
Action: func(cCtx *cli.Context) error {
args := cCtx.Args()
if args.Present() {
return cli.ShowCommandHelp(cCtx, args.First())
}
cli.ShowSubcommandHelp(cCtx)
return nil
}, },
} }
} }

View File

@@ -45,17 +45,17 @@ func TestE2EIssue130Install(t *testing.T) {
) )
runMatrixSuite( runMatrixSuite(
t, t,
"alr install {package}+alr-{repo}", "alr install {package}+{repo}",
COMMON_SYSTEMS, COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) { func(t *testing.T, r capytest.Runner) {
t.Parallel() t.Parallel()
defaultPrepare(t, r) defaultPrepare(t, r)
r.Command("sudo", "alr", "in", fmt.Sprintf("foo-pkg+alr-%s", REPO_NAME_FOR_E2E_TESTS)). r.Command("sudo", "alr", "in", fmt.Sprintf("foo-pkg+%s", REPO_NAME_FOR_E2E_TESTS)).
ExpectSuccess(). ExpectSuccess().
Run(t) Run(t)
r.Command("sudo", "alr", "in", fmt.Sprintf("bar-pkg+alr-%s", "NOT_REPO_NAME_FOR_E2E_TESTS")). r.Command("sudo", "alr", "in", fmt.Sprintf("bar-pkg+%s", "NOT_REPO_NAME_FOR_E2E_TESTS")).
ExpectFailure(). ExpectFailure().
Run(t) Run(t)
}, },

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.

2
go.mod
View File

@@ -4,6 +4,7 @@ go 1.24.4
require ( require (
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3
gitea.plemya-x.ru/xpamych/vercmp v0.0.1
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PuerkitoBio/purell v1.2.0 github.com/PuerkitoBio/purell v1.2.0
github.com/alecthomas/chroma/v2 v2.9.1 github.com/alecthomas/chroma/v2 v2.9.1
@@ -36,7 +37,6 @@ require (
github.com/vmihailenco/msgpack/v5 v5.3.5 github.com/vmihailenco/msgpack/v5 v5.3.5
go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9
go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9 go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.36.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/sys v0.33.0 golang.org/x/sys v0.33.0

6
go.sum
View File

@@ -21,6 +21,8 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGq
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8= gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw= gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw=
gitea.plemya-x.ru/xpamych/vercmp v0.0.1 h1:tFQzsPfnQQDQ3jrqW0UwUSbK+HwJuq0sA0GfnvIkatw=
gitea.plemya-x.ru/xpamych/vercmp v0.0.1/go.mod h1:z9qQ4QJDou1AULVKPIW5blu/jT+O3O5HpTV8aujWSIM=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
@@ -432,14 +434,10 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
go.alt-gnome.ru/capytest v0.0.2 h1:clmvIqmYS86hhA1rsvivSSPpfOFkJTpbn38EQP7I3E8=
go.alt-gnome.ru/capytest v0.0.2/go.mod h1:lvxPx3H6h+LPnStBFblgoT2wkjv0wbug3S14troykEg=
go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 h1:NST+V5LV/eLgs0p6PsuvfHiZ4UrIWqftCdifO8zgg0g= go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 h1:NST+V5LV/eLgs0p6PsuvfHiZ4UrIWqftCdifO8zgg0g=
go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:qiM8LARP+JBZr5mrDoVylOoqjrN0MAzvZ21NR9qMc0Y= go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:qiM8LARP+JBZr5mrDoVylOoqjrN0MAzvZ21NR9qMc0Y=
go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9 h1:VZclgdJxARvhZ6PIWWW2hQ6Ge4XeE36pzUr/U/y62bE= go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9 h1:VZclgdJxARvhZ6PIWWW2hQ6Ge4XeE36pzUr/U/y62bE=
go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:Wpq1Ny3eMzADJpMJArA2TZGZbsviUBmawtEPcxnoerg= go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:Wpq1Ny3eMzADJpMJArA2TZGZbsviUBmawtEPcxnoerg=
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY=
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=

View File

@@ -49,11 +49,26 @@ func HelperCmd() *cli.Command {
}, },
} }
helperHelpCmd := &cli.Command{
Name: "help",
Aliases: []string{"h"},
Usage: gotext.Get("Shows a list of commands or help for one command"),
ArgsUsage: "[command]",
Action: func(cCtx *cli.Context) error {
args := cCtx.Args()
if args.Present() {
return cli.ShowCommandHelp(cCtx, args.First())
}
cli.ShowSubcommandHelp(cCtx)
return nil
},
}
return &cli.Command{ return &cli.Command{
Name: "helper", Name: "helper",
Usage: gotext.Get("Run a ALR helper command"), Usage: gotext.Get("Run a ALR helper command"),
ArgsUsage: `<helper_name|"list">`, ArgsUsage: `<helper_name|"list">`,
Subcommands: []*cli.Command{helperListCmd}, Subcommands: []*cli.Command{helperListCmd, helperHelpCmd},
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "dest-dir", Name: "dest-dir",
@@ -100,7 +115,6 @@ func HelperCmd() *cli.Command {
return helper(hc, c.Args().First(), c.Args().Slice()[1:]) return helper(hc, c.Args().First(), c.Args().Slice()[1:])
}, },
CustomHelpTemplate: cli.CommandHelpTemplate,
BashComplete: func(ctx *cli.Context) { BashComplete: func(ctx *cli.Context) {
for name := range helpers.Helpers { for name := range helpers.Helpers {
fmt.Println(name) fmt.Println(name)

View File

@@ -32,6 +32,7 @@ import (
"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/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/translations"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
) )
@@ -42,6 +43,7 @@ func InternalBuildCmd() *cli.Command {
Hidden: true, Hidden: true,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
logger.SetupForGoPlugin() logger.SetupForGoPlugin()
translations.Setup()
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())
@@ -81,7 +83,7 @@ func InternalReposCmd() *cli.Command {
Hidden: true, Hidden: true,
Action: utils.RootNeededAction(func(ctx *cli.Context) error { Action: utils.RootNeededAction(func(ctx *cli.Context) error {
logger.SetupForGoPlugin() logger.SetupForGoPlugin()
translations.Setup()
deps, err := appbuilder. deps, err := appbuilder.
New(ctx.Context). New(ctx.Context).
@@ -115,6 +117,7 @@ func InternalInstallCmd() *cli.Command {
Hidden: true, Hidden: true,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
logger.SetupForGoPlugin() logger.SetupForGoPlugin()
translations.Setup()
// Запуск от текущего пользователя, повышение прав будет через sudo при необходимости // Запуск от текущего пользователя, повышение прав будет через sudo при необходимости

View File

@@ -565,73 +565,173 @@ func (b *Builder) BuildALRDeps(
}, },
depends []string, depends []string,
) (buildDeps []*BuiltDep, repoDeps []string, err error) { ) (buildDeps []*BuiltDep, repoDeps []string, err error) {
if len(depends) > 0 { if len(depends) == 0 {
slog.Info(gotext.Get("Installing dependencies")) return nil, nil, nil
}
found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей
if err != nil { slog.Info(gotext.Get("Installing dependencies"))
return nil, nil, fmt.Errorf("failed FindPkgs: %w", err)
} // Шаг 1: Рекурсивно разрешаем ВСЕ зависимости
repoDeps = notFound depTree, systemDeps, err := b.ResolveDependencyTree(ctx, input, depends)
if err != nil {
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез return nil, nil, fmt.Errorf("failed to resolve dependency tree: %w", err)
// Для зависимостей указываем isDependency = true }
pkgs := cliutils.FlattenPkgsWithContext(
ctx, // Системные зависимости возвращаем как repoDeps
found, repoDeps = systemDeps
"install",
input.BuildOpts().Interactive, // Шаг 2: Собираем список всех пакетов из дерева для топологической сортировки
true, allFound := make(map[string][]alrsh.Package)
) for baseName, node := range depTree {
type item struct { allFound[baseName] = []alrsh.Package{*node.Package}
pkg *alrsh.Package }
packages []string
} // Шаг 3: Топологическая сортировка (от корней к листьям)
pkgsMap := make(map[string]*item) sortedPkgs, err := TopologicalSort(depTree, allFound)
for _, pkg := range pkgs { if err != nil {
name := pkg.BasePkgName return nil, nil, fmt.Errorf("failed to sort dependencies: %w", err)
if name == "" { }
name = pkg.Name
} // Шаг 4: Собираем пакеты в правильном порядке, проверяя кеш
if pkgsMap[name] == nil { for _, basePkgName := range sortedPkgs {
pkgsMap[name] = &item{ node := depTree[basePkgName]
pkg: &pkg, if node == nil {
} continue
} }
pkgsMap[name].packages = append(
pkgsMap[name].packages, pkg := node.Package
pkg.Name,
) // Находим ВСЕ подпакеты с этим BasePkgName
} allSubpkgs, err := b.findAllSubpackages(ctx, basePkgName, pkg.Repository)
if err != nil {
for basePkgName := range pkgsMap { return nil, nil, fmt.Errorf("failed to find subpackages for %s: %w", basePkgName, err)
pkg := pkgsMap[basePkgName].pkg }
res, err := b.BuildPackageFromDb(
ctx, // Проверяем кеш для ВСЕХ подпакетов
&BuildPackageFromDbArgs{ scriptInfo := b.scriptResolver.ResolveScript(ctx, pkg)
Package: pkg, buildInput := &BuildInput{
Packages: pkgsMap[basePkgName].packages, script: scriptInfo.Script,
BuildArgs: BuildArgs{ repository: scriptInfo.Repository,
Opts: input.BuildOpts(), packages: allSubpkgs,
Info: input.OSRelease(), pkgFormat: input.PkgFormat(),
PkgFormat_: input.PkgFormat(), opts: input.BuildOpts(),
}, info: input.OSRelease(),
}, }
)
if err != nil { cachedDeps, allInCache, err := b.checkCacheForAllSubpackages(ctx, buildInput, basePkgName, allSubpkgs)
return nil, nil, fmt.Errorf("failed build package from db: %w", err) if err != nil {
} return nil, nil, err
}
buildDeps = append(buildDeps, res...)
} if allInCache {
// Все подпакеты в кеше, используем их
buildDeps = append(buildDeps, cachedDeps...)
continue
}
// Собираем пакет (без рекурсивной сборки зависимостей, так как они уже собраны)
res, err := b.BuildPackageFromDb(
ctx,
&BuildPackageFromDbArgs{
Package: pkg,
Packages: allSubpkgs,
BuildArgs: BuildArgs{
Opts: input.BuildOpts(),
Info: input.OSRelease(),
PkgFormat_: input.PkgFormat(),
},
},
)
if err != nil {
return nil, nil, fmt.Errorf("failed build package from db: %w", err)
}
buildDeps = append(buildDeps, res...)
} }
repoDeps = removeDuplicates(repoDeps)
buildDeps = removeDuplicates(buildDeps) buildDeps = removeDuplicates(buildDeps)
return buildDeps, repoDeps, nil return buildDeps, repoDeps, nil
} }
// findAllSubpackages находит все подпакеты для базового пакета
func (b *Builder) findAllSubpackages(ctx context.Context, basePkgName, repository string) ([]string, error) {
// Запрашиваем все пакеты с этим basepkg_name
pkgs, _, err := b.repos.FindPkgs(ctx, []string{basePkgName})
if err != nil {
return nil, err
}
var subpkgs []string
seen := make(map[string]bool)
for _, pkgList := range pkgs {
for _, pkg := range pkgList {
// Проверяем, что это пакет из нужного репозитория
if pkg.Repository == repository {
pkgBase := pkg.BasePkgName
if pkgBase == "" {
pkgBase = pkg.Name
}
// Добавляем только если это пакет с нужным BasePkgName
if pkgBase == basePkgName && !seen[pkg.Name] {
subpkgs = append(subpkgs, pkg.Name)
seen[pkg.Name] = true
}
}
}
}
return subpkgs, nil
}
// checkCacheForAllSubpackages проверяет кеш для всех подпакетов
func (b *Builder) checkCacheForAllSubpackages(
ctx context.Context,
buildInput *BuildInput,
basePkgName string,
subpkgs []string,
) ([]*BuiltDep, bool, error) {
var cachedDeps []*BuiltDep
allInCache := true
// Получаем информацию обо всех подпакетах
pkgsInfo, _, err := b.repos.FindPkgs(ctx, subpkgs)
if err != nil {
return nil, false, fmt.Errorf("failed to find subpackages info: %w", err)
}
for _, pkgName := range subpkgs {
var pkgForCheck *alrsh.Package
// Находим Package для подпакета
if pkgList, ok := pkgsInfo[pkgName]; ok && len(pkgList) > 0 {
pkgForCheck = &pkgList[0]
}
if pkgForCheck != nil {
pkgPath, found, err := b.cacheExecutor.CheckForBuiltPackage(ctx, buildInput, pkgForCheck)
if err != nil {
return nil, false, fmt.Errorf("failed to check cache: %w", err)
}
if found {
slog.Info(gotext.Get("Using cached package"), "name", pkgName, "path", pkgPath)
cachedDeps = append(cachedDeps, &BuiltDep{
Name: pkgName,
Path: pkgPath,
})
} else {
allInCache = false
break
}
}
}
return cachedDeps, allInCache && len(cachedDeps) > 0, nil
}
func (i *Builder) installBuildDeps( func (i *Builder) installBuildDeps(
ctx context.Context, ctx context.Context,
input interface { input interface {

View File

@@ -40,7 +40,12 @@ func (c *Cache) CheckForBuiltPackage(
return "", false, err return "", false, err
} }
pkgPath := filepath.Join(getBaseDir(c.cfg, vars.Name), filename) // Для подпакетов используем BasePkgName, чтобы искать в правильной директории
baseName := vars.BasePkgName
if baseName == "" {
baseName = vars.Name
}
pkgPath := filepath.Join(getBaseDir(c.cfg, baseName), filename)
_, err = os.Stat(pkgPath) _, err = os.Stat(pkgPath)
if err != nil { if err != nil {

View File

@@ -0,0 +1,193 @@
// 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 build
import (
"context"
"fmt"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
// DependencyNode представляет узел в дереве зависимостей
type DependencyNode struct {
Package *alrsh.Package
BasePkgName string
Dependencies []string // Имена зависимостей
}
// ResolveDependencyTree рекурсивно разрешает все зависимости и возвращает
// плоский список всех уникальных пакетов, необходимых для сборки
// и список системных зависимостей (не найденных в ALR-репозиториях)
func (b *Builder) ResolveDependencyTree(
ctx context.Context,
input interface {
OsInfoProvider
PkgFormatProvider
},
initialPkgs []string,
) (map[string]*DependencyNode, []string, error) {
resolved := make(map[string]*DependencyNode)
visited := make(map[string]bool)
systemDeps := make(map[string]bool) // Для дедупликации системных зависимостей
var resolve func(pkgNames []string) error
resolve = func(pkgNames []string) error {
if len(pkgNames) == 0 {
return nil
}
// Находим пакеты
found, notFound, err := b.repos.FindPkgs(ctx, pkgNames)
if err != nil {
return fmt.Errorf("failed to find packages: %w", err)
}
// Собираем системные зависимости (не найденные в ALR)
for _, pkgName := range notFound {
systemDeps[pkgName] = true
}
// Обрабатываем найденные пакеты
for pkgName, pkgList := range found {
if visited[pkgName] {
continue
}
visited[pkgName] = true
// Берем первый пакет из списка (или можно добавить выбор пользователя)
if len(pkgList) == 0 {
continue
}
pkg := pkgList[0]
// Определяем базовое имя пакета
baseName := pkg.BasePkgName
if baseName == "" {
baseName = pkg.Name
}
// Если уже обработали этот базовый пакет, пропускаем
if resolved[baseName] != nil {
continue
}
// Получаем зависимости для этого дистрибутива
// Пакет из БД уже содержит разрешенные значения для текущего дистрибутива
deps := pkg.Depends.Resolved()
buildDeps := pkg.BuildDepends.Resolved()
// Объединяем зависимости
allDeps := append([]string{}, deps...)
allDeps = append(allDeps, buildDeps...)
// Добавляем узел в resolved
resolved[baseName] = &DependencyNode{
Package: &pkg,
BasePkgName: baseName,
Dependencies: allDeps,
}
// Рекурсивно разрешаем зависимости
if len(allDeps) > 0 {
if err := resolve(allDeps); err != nil {
return err
}
}
}
return nil
}
// Начинаем разрешение с начальных пакетов
if err := resolve(initialPkgs); err != nil {
return nil, nil, err
}
// Преобразуем map в слайс для системных зависимостей
var systemDepsList []string
for dep := range systemDeps {
systemDepsList = append(systemDepsList, dep)
}
return resolved, systemDepsList, nil
}
// TopologicalSort выполняет топологическую сортировку пакетов по зависимостям
// Возвращает список базовых имен пакетов в порядке сборки (от корней к листьям)
func TopologicalSort(nodes map[string]*DependencyNode, allPkgs map[string][]alrsh.Package) ([]string, error) {
// Список для результата
var result []string
// Множество посещенных узлов
visited := make(map[string]bool)
// Множество узлов в текущем пути (для обнаружения циклов)
inStack := make(map[string]bool)
var visit func(basePkgName string) error
visit = func(basePkgName string) error {
if visited[basePkgName] {
return nil
}
if inStack[basePkgName] {
return fmt.Errorf("circular dependency detected: %s", basePkgName)
}
node := nodes[basePkgName]
if node == nil {
// Это системный пакет, игнорируем
return nil
}
inStack[basePkgName] = true
// Посещаем все зависимости
for _, dep := range node.Dependencies {
// Находим базовое имя для зависимости
depBaseName := dep
// Проверяем, есть ли этот пакет в allPkgs
if pkgs, ok := allPkgs[dep]; ok && len(pkgs) > 0 {
if pkgs[0].BasePkgName != "" {
depBaseName = pkgs[0].BasePkgName
}
}
if err := visit(depBaseName); err != nil {
return err
}
}
inStack[basePkgName] = false
visited[basePkgName] = true
result = append(result, basePkgName)
return nil
}
// Посещаем все узлы
for basePkgName := range nodes {
if err := visit(basePkgName); err != nil {
return nil, err
}
}
return result, nil
}

View File

@@ -42,6 +42,15 @@ func getDirs(
cfg Config, cfg Config,
scriptPath string, scriptPath string,
basePkg string, basePkg string,
) (types.Directories, error) {
return getDirsForPackage(cfg, scriptPath, basePkg, "")
}
func getDirsForPackage(
cfg Config,
scriptPath string,
basePkg string,
packageName string,
) (types.Directories, error) { ) (types.Directories, error) {
pkgsDir := cfg.GetPaths().PkgsDir pkgsDir := cfg.GetPaths().PkgsDir
@@ -50,10 +59,18 @@ func getDirs(
return types.Directories{}, err return types.Directories{}, err
} }
baseDir := filepath.Join(pkgsDir, basePkg) baseDir := filepath.Join(pkgsDir, basePkg)
// Для подпакетов используем отдельную директорию pkg_<имя_подпакета>
// Для обычных пакетов используем просто pkg
pkgDirName := "pkg"
if packageName != "" {
pkgDirName = "pkg_" + packageName
}
return types.Directories{ return types.Directories{
BaseDir: getBaseDir(cfg, basePkg), BaseDir: getBaseDir(cfg, basePkg),
SrcDir: getSrcDir(cfg, basePkg), SrcDir: getSrcDir(cfg, basePkg),
PkgDir: filepath.Join(baseDir, "pkg"), PkgDir: filepath.Join(baseDir, pkgDirName),
ScriptDir: getScriptDir(scriptPath), ScriptDir: getScriptDir(scriptPath),
}, nil }, nil
} }

View File

@@ -18,8 +18,16 @@ package build
import ( import (
"context" "context"
"fmt"
"log/slog"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/xpamych/vercmp"
"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/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
) )
func NewInstaller(mgr manager.Manager) *Installer { func NewInstaller(mgr manager.Manager) *Installer {
@@ -58,3 +66,44 @@ func (i *Installer) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) (
return filteredPackages, nil return filteredPackages, nil
} }
func (i *Installer) FilterPackagesByVersion(ctx context.Context, packages []alrsh.Package, osRelease *distro.OSRelease) ([]alrsh.Package, error) {
installedPkgs, err := i.mgr.ListInstalled(nil)
if err != nil {
return nil, fmt.Errorf("failed to list installed packages: %w", err)
}
var filteredPackages []alrsh.Package
for _, pkg := range packages {
alrPkgName := fmt.Sprintf("%s+%s", pkg.Name, pkg.Repository)
installedVer, isInstalled := installedPkgs[alrPkgName]
if !isInstalled {
filteredPackages = append(filteredPackages, pkg)
continue
}
repoVer := pkg.Version
releaseStr := overrides.ReleasePlatformSpecific(pkg.Release, osRelease)
if pkg.Release != 0 && pkg.Epoch == 0 {
repoVer = fmt.Sprintf("%s-%s", pkg.Version, releaseStr)
} else if pkg.Release != 0 && pkg.Epoch != 0 {
repoVer = fmt.Sprintf("%d:%s-%s", pkg.Epoch, pkg.Version, releaseStr)
}
cmp := vercmp.Compare(repoVer, installedVer)
if cmp > 0 {
slog.Info(gotext.Get("Package %s is installed with older version %s, will rebuild with version %s", alrPkgName, installedVer, repoVer))
filteredPackages = append(filteredPackages, pkg)
} else if cmp == 0 {
slog.Info(gotext.Get("Package %s is already installed with version %s, skipping build", alrPkgName, installedVer))
} else {
slog.Info(gotext.Get("Package %s is installed with newer version %s (repo has %s), skipping build", alrPkgName, installedVer, repoVer))
}
}
return filteredPackages, nil
}

View File

@@ -21,6 +21,7 @@ import (
"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/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/types" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
) )
@@ -34,6 +35,7 @@ type InstallerExecutor interface {
Install(ctx context.Context, pkgs []string, opts *manager.Opts) error Install(ctx context.Context, pkgs []string, opts *manager.Opts) error
Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error
RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error)
FilterPackagesByVersion(ctx context.Context, packages []alrsh.Package, osRelease *distro.OSRelease) ([]alrsh.Package, error)
} }
type ScriptExecutor interface { type ScriptExecutor interface {

View File

@@ -24,6 +24,7 @@ import (
"context" "context"
"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/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/types" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
"github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin"
) )
@@ -205,6 +206,38 @@ func (s *InstallerExecutorRPCServer) RemoveAlreadyInstalled(args *InstallerExecu
return nil return nil
} }
type InstallerExecutorFilterPackagesByVersionArgs struct {
Packages []alrsh.Package
OsRelease *distro.OSRelease
}
type InstallerExecutorFilterPackagesByVersionResp struct {
Result0 []alrsh.Package
}
func (s *InstallerExecutorRPC) FilterPackagesByVersion(ctx context.Context, packages []alrsh.Package, osRelease *distro.OSRelease) ([]alrsh.Package, error) {
var resp *InstallerExecutorFilterPackagesByVersionResp
err := s.client.Call("Plugin.FilterPackagesByVersion", &InstallerExecutorFilterPackagesByVersionArgs{
Packages: packages,
OsRelease: osRelease,
}, &resp)
if err != nil {
return nil, err
}
return resp.Result0, nil
}
func (s *InstallerExecutorRPCServer) FilterPackagesByVersion(args *InstallerExecutorFilterPackagesByVersionArgs, resp *InstallerExecutorFilterPackagesByVersionResp) error {
result0, err := s.Impl.FilterPackagesByVersion(context.Background(), args.Packages, args.OsRelease)
if err != nil {
return err
}
*resp = InstallerExecutorFilterPackagesByVersionResp{
Result0: result0,
}
return nil
}
type ScriptExecutorReadScriptArgs struct { type ScriptExecutorReadScriptArgs struct {
ScriptPath string ScriptPath string
} }

View File

@@ -130,12 +130,34 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
packageName = vars.Name packageName = vars.Name
} }
// Для каждого подпакета создаём отдельную директорию
pkgDirs, err := getDirsForPackage(e.cfg, sf.Path(), basePkg, packageName)
if err != nil {
return nil, err
}
// Создаём директорию для подпакета
if err := os.MkdirAll(pkgDirs.PkgDir, 0o755); err != nil {
return nil, err
}
// Обновляем переменную окружения $pkgdir для текущего подпакета
setPkgdirCmd := fmt.Sprintf("pkgdir='%s'", pkgDirs.PkgDir)
setPkgdirScript, err := syntax.NewParser().Parse(strings.NewReader(setPkgdirCmd), "")
if err != nil {
return nil, err
}
err = runner.Run(ctx, setPkgdirScript)
if err != nil {
return nil, err
}
pkgFormat := input.pkgFormat pkgFormat := input.pkgFormat
funcOut, err := e.ExecutePackageFunctions( funcOut, err := e.ExecutePackageFunctions(
ctx, ctx,
dec, dec,
dirs, pkgDirs,
packageName, packageName,
) )
if err != nil { if err != nil {
@@ -148,7 +170,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
ctx, ctx,
input, input,
vars, vars,
dirs, pkgDirs,
append( append(
repoDeps, repoDeps,
GetBuiltName(builtDeps)..., GetBuiltName(builtDeps)...,
@@ -165,17 +187,32 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
} }
pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета
pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету pkgPath := filepath.Join(pkgDirs.BaseDir, pkgName) // Определяем путь к пакету
slog.Info(gotext.Get("Creating package file"), "path", pkgPath, "name", pkgName)
pkgFile, err := os.Create(pkgPath) pkgFile, err := os.Create(pkgPath)
if err != nil { if err != nil {
slog.Error(gotext.Get("Failed to create package file"), "path", pkgPath, "error", err)
return nil, err
}
defer pkgFile.Close()
slog.Info(gotext.Get("Packaging with nfpm"), "format", pkgFormat)
err = packager.Package(pkgInfo, pkgFile)
if err != nil {
slog.Error(gotext.Get("Failed to create package"), "path", pkgPath, "error", err)
return nil, err return nil, err
} }
err = packager.Package(pkgInfo, pkgFile) slog.Info(gotext.Get("Package created successfully"), "path", pkgPath)
if err != nil {
// Проверяем, что файл действительно существует
if _, err := os.Stat(pkgPath); err != nil {
slog.Error(gotext.Get("Package file not found after creation"), "path", pkgPath, "error", err)
return nil, err return nil, err
} }
slog.Info(gotext.Get("Package file verified to exist"), "path", pkgPath)
builtDeps = append(builtDeps, &BuiltDep{ builtDeps = append(builtDeps, &BuiltDep{
Name: vars.Name, Name: vars.Name,

View File

@@ -49,20 +49,29 @@ import (
// Функция prepareDirs подготавливает директории для сборки. // Функция prepareDirs подготавливает директории для сборки.
func prepareDirs(dirs types.Directories) error { func prepareDirs(dirs types.Directories) error {
// Пробуем удалить базовую директорию, если она существует // Удаляем только директории источников и упаковки, не трогаем файлы пакетов в BaseDir
err := os.RemoveAll(dirs.BaseDir) err := os.RemoveAll(dirs.SrcDir)
if err != nil { if err != nil {
// Если не можем удалить (например, принадлежит root), логируем и продолжаем slog.Debug("Failed to remove src directory", "path", dirs.SrcDir, "error", err)
// Новые директории будут созданы или перезаписаны
slog.Debug("Failed to remove base directory", "path", dirs.BaseDir, "error", err)
} }
err = os.RemoveAll(dirs.PkgDir)
if err != nil {
slog.Debug("Failed to remove pkg directory", "path", dirs.PkgDir, "error", err)
}
// Создаем базовую директорию для пакета с setgid битом
err = utils.EnsureTempDirWithRootOwner(dirs.BaseDir, 0o2775)
if err != nil {
return err
}
// Создаем директории с правильным владельцем для /tmp/alr с setgid битом // Создаем директории с правильным владельцем для /tmp/alr с setgid битом
err = utils.EnsureTempDirWithRootOwner(dirs.SrcDir, 0o2775) err = utils.EnsureTempDirWithRootOwner(dirs.SrcDir, 0o2775)
if err != nil { if err != nil {
return err return err
} }
// Создаем директорию для пакетов с setgid битом // Создаем директорию для пакетов с setgid битом
return utils.EnsureTempDirWithRootOwner(dirs.PkgDir, 0o2775) return utils.EnsureTempDirWithRootOwner(dirs.PkgDir, 0o2775)
} }
@@ -169,15 +178,16 @@ func normalizeContents(contents []*files.Content) {
} }
} }
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`) var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+(?P<repo>.+)$`)
func getBasePkgInfo(vars *alrsh.Package, input interface { func getBasePkgInfo(vars *alrsh.Package, input interface {
RepositoryProvider RepositoryProvider
OsInfoProvider OsInfoProvider
}, },
) *nfpm.Info { ) *nfpm.Info {
repo := input.Repository()
return &nfpm.Info{ return &nfpm.Info{
Name: fmt.Sprintf("%s+alr-%s", vars.Name, input.Repository()), Name: fmt.Sprintf("%s+%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

@@ -0,0 +1,158 @@
// 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 build
import (
"testing"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
type mockInput struct {
repo string
osInfo *distro.OSRelease
}
func (m *mockInput) Repository() string {
return m.repo
}
func (m *mockInput) OSRelease() *distro.OSRelease {
return m.osInfo
}
func TestGetBasePkgInfo(t *testing.T) {
tests := []struct {
name string
packageName string
repoName string
expectedName string
}{
{
name: "обычный репозиторий",
packageName: "test-package",
repoName: "default",
expectedName: "test-package+default",
},
{
name: "репозиторий с alr- префиксом",
packageName: "test-package",
repoName: "alr-default",
expectedName: "test-package+alr-default",
},
{
name: "репозиторий с двойным alr- префиксом",
packageName: "test-package",
repoName: "alr-alr-repo",
expectedName: "test-package+alr-alr-repo",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pkg := &alrsh.Package{
Name: tt.packageName,
Version: "1.0.0",
Release: 1,
}
input := &mockInput{
repo: tt.repoName,
osInfo: &distro.OSRelease{
ID: "test",
},
}
info := getBasePkgInfo(pkg, input)
if info.Name != tt.expectedName {
t.Errorf("getBasePkgInfo() имя пакета = %v, ожидается %v", info.Name, tt.expectedName)
}
})
}
}
func TestRegexpALRPackageName(t *testing.T) {
tests := []struct {
name string
packageName string
expectedPkg string
expectedRepo string
shouldMatch bool
}{
{
name: "новый формат - обычный репозиторий",
packageName: "test-package+default",
expectedPkg: "test-package",
expectedRepo: "default",
shouldMatch: true,
},
{
name: "новый формат - alr-default репозиторий",
packageName: "test-package+alr-default",
expectedPkg: "test-package",
expectedRepo: "alr-default",
shouldMatch: true,
},
{
name: "новый формат - двойной alr- префикс",
packageName: "test-package+alr-alr-repo",
expectedPkg: "test-package",
expectedRepo: "alr-alr-repo",
shouldMatch: true,
},
{
name: "некорректный формат - без плюса",
packageName: "test-package",
shouldMatch: false,
},
{
name: "некорректный формат - пустое имя пакета",
packageName: "+repo",
shouldMatch: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
matches := RegexpALRPackageName.FindStringSubmatch(tt.packageName)
if tt.shouldMatch {
if matches == nil {
t.Errorf("RegexpALRPackageName должен найти совпадение для %q", tt.packageName)
return
}
packageName := matches[RegexpALRPackageName.SubexpIndex("package")]
repoName := matches[RegexpALRPackageName.SubexpIndex("repo")]
if packageName != tt.expectedPkg {
t.Errorf("RegexpALRPackageName извлеченное имя пакета = %v, ожидается %v", packageName, tt.expectedPkg)
}
if repoName != tt.expectedRepo {
t.Errorf("RegexpALRPackageName извлеченное имя репозитория = %v, ожидается %v", repoName, tt.expectedRepo)
}
} else {
if matches != nil {
t.Errorf("RegexpALRPackageName не должен найти совпадение для %q", tt.packageName)
}
}
})
}
}

View File

@@ -42,7 +42,7 @@ type AppDeps struct {
func (d *AppDeps) Defer() { func (d *AppDeps) Defer() {
if d.DB != nil { if d.DB != nil {
if err := d.DB.Close(); err != nil { if err := d.DB.Close(); err != nil {
slog.Warn("failed to close db", "err", err) slog.Warn(gotext.Get("failed to close db"), "err", err)
} }
} }
} }

View File

@@ -100,3 +100,28 @@ func GetCommandHelpTemplate() string {
gotext.Get("OPTIONS"), gotext.Get("OPTIONS"),
) )
} }
func GetSubcommandHelpTemplate() string {
return fmt.Sprintf(`%s:
{{template "helpNameTemplate" .}}
%s:
{{.HelpName}} %s [%s] {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}
{{if .Description}}
%s:
{{template "descriptionTemplate" .}}{{end}}
{{- if len .Authors}}
%s{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}}
%s:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
%s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
%s:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}}
%s:
{{template "copyrightTemplate" .}}{{end}}
`, gotext.Get("NAME"), gotext.Get("USAGE"), gotext.Get("command"), gotext.Get("command options"), gotext.Get("arguments"), gotext.Get("DESCRIPTION"), gotext.Get("AUTHOR"), gotext.Get("COMMANDS"), gotext.Get("OPTIONS"), gotext.Get("OPTIONS"), gotext.Get("COPYRIGHT"))
}

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

@@ -31,6 +31,7 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
"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/fsutils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
) )
@@ -57,21 +58,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) 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 { slog.Info(gotext.Get("Cache directory does not exist, creating it"))
// Не смогли создать - вернём ошибку, пользователь должен использовать alr fix if err := fsutils.EnsureTempDirWithRootOwner(dbDir, 0o2775); err != nil {
return fmt.Errorf("cache directory does not exist, please run 'alr fix' to create it: %w", mkErr) return fmt.Errorf("failed to create cache directory: %w", err)
} }
} else { } else {
return fmt.Errorf("failed to check database directory: %w", err) 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)

100
internal/fsutils/dirs.go Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
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)
}

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 fsutils
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

@@ -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

@@ -47,9 +47,9 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrs
name := parts[1] name := parts[1]
result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo) result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo)
case strings.Contains(pkgName, "+alr-"): case strings.Contains(pkgName, "+"):
// pkg+alr-repo // pkg+repo
parts := strings.SplitN(pkgName, "+alr-", 2) parts := strings.SplitN(pkgName, "+", 2)
name := parts[0] name := parts[0]
repo := parts[1] repo := parts[1]
result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo) result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo)

View File

@@ -36,7 +36,7 @@ import (
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"go.elara.ws/vercmp" "gitea.plemya-x.ru/xpamych/vercmp"
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
@@ -420,13 +420,13 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
case actionDelete: case actionDelete:
scriptFl, err := oldCommit.File(action.File) scriptFl, err := oldCommit.File(action.File)
if err != nil { if err != nil {
slog.Warn("Failed to get deleted file from old commit", "file", action.File, "error", err) slog.Warn(gotext.Get("Failed to get deleted file from old commit"), "file", action.File, "error", err)
continue continue
} }
r, err := scriptFl.Reader() r, err := scriptFl.Reader()
if err != nil { if err != nil {
slog.Warn("Failed to read deleted file", "file", action.File, "error", err) slog.Warn(gotext.Get("Failed to read deleted file"), "file", action.File, "error", err)
continue continue
} }
@@ -445,13 +445,13 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
case actionUpdate: case actionUpdate:
scriptFl, err := newCommit.File(action.File) scriptFl, err := newCommit.File(action.File)
if err != nil { if err != nil {
slog.Warn("Failed to get updated file from new commit", "file", action.File, "error", err) slog.Warn(gotext.Get("Failed to get updated file from new commit"), "file", action.File, "error", err)
continue continue
} }
r, err := scriptFl.Reader() r, err := scriptFl.Reader()
if err != nil { if err != nil {
slog.Warn("Failed to read updated file", "file", action.File, "error", err) slog.Warn(gotext.Get("Failed to read updated file"), "file", action.File, "error", err)
continue continue
} }
@@ -505,7 +505,7 @@ func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir s
} }
if len(matches) == 0 { if len(matches) == 0 {
slog.Warn("No alr.sh files found in repository", "repo", repo.Name) slog.Warn(gotext.Get("No alr.sh files found in repository"), "repo", repo.Name)
return nil return nil
} }

View File

@@ -177,3 +177,333 @@ func filesFindCmd(hc interp.HandlerContext, cmd string, args []string) error {
return outputFiles(hc, foundFiles) return outputFiles(hc, foundFiles)
} }
func filesFindBinCmd(hc interp.HandlerContext, cmd string, args []string) error {
namePattern := "*"
if len(args) > 0 {
namePattern = args[0]
}
binPath := "./usr/bin/"
realPath := path.Join(hc.Dir, binPath)
if err := validateDir(realPath, "files-find-bin"); err != nil {
return err
}
var binFiles []string
err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
relPath, relErr := makeRelativePath(hc.Dir, p)
if relErr != nil {
return relErr
}
binFiles = append(binFiles, relPath)
}
return nil
})
if err != nil {
return fmt.Errorf("files-find-bin: %w", err)
}
return outputFiles(hc, binFiles)
}
func filesFindLibCmd(hc interp.HandlerContext, cmd string, args []string) error {
namePattern := "*"
if len(args) > 0 {
namePattern = args[0]
}
libPaths := []string{"./usr/lib/", "./usr/lib64/"}
var libFiles []string
for _, libPath := range libPaths {
realPath := path.Join(hc.Dir, libPath)
if _, err := os.Stat(realPath); os.IsNotExist(err) {
continue
}
err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
relPath, relErr := makeRelativePath(hc.Dir, p)
if relErr != nil {
return relErr
}
libFiles = append(libFiles, relPath)
}
return nil
})
if err != nil {
return fmt.Errorf("files-find-lib: %w", err)
}
}
return outputFiles(hc, libFiles)
}
func filesFindIncludeCmd(hc interp.HandlerContext, cmd string, args []string) error {
namePattern := "*"
if len(args) > 0 {
namePattern = args[0]
}
includePath := "./usr/include/"
realPath := path.Join(hc.Dir, includePath)
if err := validateDir(realPath, "files-find-include"); err != nil {
return err
}
var includeFiles []string
err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
relPath, relErr := makeRelativePath(hc.Dir, p)
if relErr != nil {
return relErr
}
includeFiles = append(includeFiles, relPath)
}
return nil
})
if err != nil {
return fmt.Errorf("files-find-include: %w", err)
}
return outputFiles(hc, includeFiles)
}
func filesFindShareCmd(hc interp.HandlerContext, cmd string, args []string) error {
namePattern := "*"
sharePath := "./usr/share/"
if len(args) > 0 {
if len(args) == 1 {
sharePath = "./usr/share/" + args[0] + "/"
} else {
sharePath = "./usr/share/" + args[0] + "/"
namePattern = args[1]
}
}
realPath := path.Join(hc.Dir, sharePath)
if err := validateDir(realPath, "files-find-share"); err != nil {
return err
}
var shareFiles []string
err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
relPath, relErr := makeRelativePath(hc.Dir, p)
if relErr != nil {
return relErr
}
shareFiles = append(shareFiles, relPath)
}
return nil
})
if err != nil {
return fmt.Errorf("files-find-share: %w", err)
}
return outputFiles(hc, shareFiles)
}
func filesFindManCmd(hc interp.HandlerContext, cmd string, args []string) error {
namePattern := "*"
manSection := "*"
if len(args) > 0 {
if len(args) == 1 {
manSection = args[0]
} else {
manSection = args[0]
namePattern = args[1]
}
}
manPath := "./usr/share/man/man" + manSection + "/"
realPath := path.Join(hc.Dir, manPath)
if err := validateDir(realPath, "files-find-man"); err != nil {
return err
}
var manFiles []string
err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
relPath, relErr := makeRelativePath(hc.Dir, p)
if relErr != nil {
return relErr
}
manFiles = append(manFiles, relPath)
}
return nil
})
if err != nil {
return fmt.Errorf("files-find-man: %w", err)
}
return outputFiles(hc, manFiles)
}
func filesFindConfigCmd(hc interp.HandlerContext, cmd string, args []string) error {
namePattern := "*"
if len(args) > 0 {
namePattern = args[0]
}
configPath := "./etc/"
realPath := path.Join(hc.Dir, configPath)
if err := validateDir(realPath, "files-find-config"); err != nil {
return err
}
var configFiles []string
err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
relPath, relErr := makeRelativePath(hc.Dir, p)
if relErr != nil {
return relErr
}
configFiles = append(configFiles, relPath)
}
return nil
})
if err != nil {
return fmt.Errorf("files-find-config: %w", err)
}
return outputFiles(hc, configFiles)
}
func filesFindSystemdCmd(hc interp.HandlerContext, cmd string, args []string) error {
namePattern := "*"
if len(args) > 0 {
namePattern = args[0]
}
systemdPath := "./usr/lib/systemd/system/"
realPath := path.Join(hc.Dir, systemdPath)
if err := validateDir(realPath, "files-find-systemd"); err != nil {
return err
}
var systemdFiles []string
err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
relPath, relErr := makeRelativePath(hc.Dir, p)
if relErr != nil {
return relErr
}
systemdFiles = append(systemdFiles, relPath)
}
return nil
})
if err != nil {
return fmt.Errorf("files-find-systemd: %w", err)
}
return outputFiles(hc, systemdFiles)
}
func filesFindSystemdUserCmd(hc interp.HandlerContext, cmd string, args []string) error {
namePattern := "*"
if len(args) > 0 {
namePattern = args[0]
}
systemdUserPath := "./usr/lib/systemd/user/"
realPath := path.Join(hc.Dir, systemdUserPath)
if err := validateDir(realPath, "files-find-systemd-user"); err != nil {
return err
}
var systemdUserFiles []string
err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
relPath, relErr := makeRelativePath(hc.Dir, p)
if relErr != nil {
return relErr
}
systemdUserFiles = append(systemdUserFiles, relPath)
}
return nil
})
if err != nil {
return fmt.Errorf("files-find-systemd-user: %w", err)
}
return outputFiles(hc, systemdUserFiles)
}
func filesFindLicenseCmd(hc interp.HandlerContext, cmd string, args []string) error {
namePattern := "*"
if len(args) > 0 {
namePattern = args[0]
}
licensePath := "./usr/share/licenses/"
realPath := path.Join(hc.Dir, licensePath)
if err := validateDir(realPath, "files-find-license"); err != nil {
return err
}
var licenseFiles []string
err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
relPath, relErr := makeRelativePath(hc.Dir, p)
if relErr != nil {
return relErr
}
licenseFiles = append(licenseFiles, relPath)
}
return nil
})
if err != nil {
return fmt.Errorf("files-find-license: %w", err)
}
return outputFiles(hc, licenseFiles)
}

View File

@@ -56,18 +56,36 @@ var Helpers = handlers.ExecFuncs{
"install-library": installLibraryCmd, "install-library": installLibraryCmd,
"git-version": gitVersionCmd, "git-version": gitVersionCmd,
"files-find": filesFindCmd, "files-find": filesFindCmd,
"files-find-lang": filesFindLangCmd, "files-find-lang": filesFindLangCmd,
"files-find-doc": filesFindDocCmd, "files-find-doc": filesFindDocCmd,
"files-find-bin": filesFindBinCmd,
"files-find-lib": filesFindLibCmd,
"files-find-include": filesFindIncludeCmd,
"files-find-share": filesFindShareCmd,
"files-find-man": filesFindManCmd,
"files-find-config": filesFindConfigCmd,
"files-find-systemd": filesFindSystemdCmd,
"files-find-systemd-user": filesFindSystemdUserCmd,
"files-find-license": filesFindLicenseCmd,
} }
// Restricted contains restricted read-only helper commands // Restricted contains restricted read-only helper commands
// that don't modify any state // that don't modify any state
var Restricted = handlers.ExecFuncs{ var Restricted = handlers.ExecFuncs{
"git-version": gitVersionCmd, "git-version": gitVersionCmd,
"files-find": filesFindCmd, "files-find": filesFindCmd,
"files-find-lang": filesFindLangCmd, "files-find-lang": filesFindLangCmd,
"files-find-doc": filesFindDocCmd, "files-find-doc": filesFindDocCmd,
"files-find-bin": filesFindBinCmd,
"files-find-lib": filesFindLibCmd,
"files-find-include": filesFindIncludeCmd,
"files-find-share": filesFindShareCmd,
"files-find-man": filesFindManCmd,
"files-find-config": filesFindConfigCmd,
"files-find-systemd": filesFindSystemdCmd,
"files-find-systemd-user": filesFindSystemdUserCmd,
"files-find-license": filesFindLicenseCmd,
} }
func installHelperCmd(prefix string, perms os.FileMode) handlers.ExecFunc { func installHelperCmd(prefix string, perms os.FileMode) handlers.ExecFunc {

View File

@@ -34,27 +34,31 @@ msgstr ""
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "" msgstr ""
#: build.go:117 #: build.go:111
msgid "Cannot get absolute script path" msgid "Cannot get absolute script path"
msgstr "" msgstr ""
#: build.go:143 #: build.go:137
msgid "Package not found" msgid "Package not found"
msgstr "" msgstr ""
#: build.go:156 #: build.go:150
msgid "Nothing to build" msgid "Nothing to build"
msgstr "" msgstr ""
#: build.go:213 #: build.go:195
msgid "Error building package" msgid "Error building package"
msgstr "" msgstr ""
#: build.go:220 #: build.go:203
msgid "Package file already moved or removed, skipping"
msgstr ""
#: build.go:209
msgid "Error moving the package" msgid "Error moving the package"
msgstr "" msgstr ""
#: build.go:224 #: build.go:213
msgid "Done" msgid "Done"
msgstr "" msgstr ""
@@ -62,71 +66,123 @@ msgstr ""
msgid "Manage config" msgid "Manage config"
msgstr "" msgstr ""
#: config.go:48 #: config.go:50
msgid "Shows a list of commands or help for one command"
msgstr ""
#: config.go:66
msgid "Show config" msgid "Show config"
msgstr "" msgstr ""
#: config.go:84 #: config.go:103
msgid "Set config value" msgid "Set config value"
msgstr "" msgstr ""
#: config.go:85 #: config.go:104
msgid "<key> <value>" msgid "<key> <value>"
msgstr "" msgstr ""
#: config.go:118 config.go:126 #: config.go:137 config.go:145 config.go:162
msgid "invalid boolean value for %s: %s" msgid "invalid boolean value for %s: %s"
msgstr "" msgstr ""
#: config.go:141 #: config.go:166
msgid "use 'repo add/remove' commands to manage repositories" msgid "use 'repo add/remove' commands to manage repositories"
msgstr "" msgstr ""
#: config.go:143 config.go:221 #: config.go:168 config.go:248
msgid "unknown config key: %s" msgid "unknown config key: %s"
msgstr "" msgstr ""
#: config.go:147 #: config.go:172
msgid "failed to save config" msgid "failed to save config"
msgstr "" msgstr ""
#: config.go:150 #: config.go:175
msgid "Successfully set %s = %s" msgid "Successfully set %s = %s"
msgstr "" msgstr ""
#: config.go:159 #: config.go:184
msgid "Get config value" msgid "Get config value"
msgstr "" msgstr ""
#: config.go:160 #: config.go:185
msgid "<key>" msgid "<key>"
msgstr "" msgstr ""
#: fix.go:39 #: fix.go:55
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "" msgstr ""
#: fix.go:60 #: fix.go:75
msgid "Clearing cache directory" msgid "Clearing cache and temporary directories"
msgstr ""
#: fix.go:64
msgid "Unable to open cache directory"
msgstr ""
#: fix.go:70
msgid "Unable to read cache directory contents"
msgstr "" msgstr ""
#: fix.go:82 #: fix.go:82
msgid "Cache directory does not exist, will create it"
msgstr ""
#: fix.go:84
msgid "Unable to open cache directory"
msgstr ""
#: fix.go:91
msgid "Unable to read cache directory contents"
msgstr ""
#: fix.go:106
msgid "Unable to remove cache item (%s) as current user, trying with sudo"
msgstr ""
#: fix.go:111
msgid "Unable to remove cache item (%s)" msgid "Unable to remove cache item (%s)"
msgstr "" msgstr ""
#: fix.go:86 #: fix.go:119
msgid "Clearing temporary directory"
msgstr ""
#: fix.go:126
msgid "Unable to remove temporary directory as current user, trying with sudo"
msgstr ""
#: fix.go:129
msgid "Unable to remove temporary directory"
msgstr ""
#: fix.go:137
msgid "Unable to create temporary directory"
msgstr ""
#: fix.go:144
msgid "Unable to create download directory"
msgstr ""
#: fix.go:151
msgid "Unable to create packages directory"
msgstr ""
#: fix.go:156
msgid "Fixing permissions on temporary files"
msgstr ""
#: fix.go:164
msgid "Unable to fix file ownership"
msgstr ""
#: fix.go:169
msgid "Unable to fix file permissions"
msgstr ""
#: fix.go:174
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "" msgstr ""
#: fix.go:90 #: fix.go:177
msgid "Creating cache directory"
msgstr ""
#: fix.go:180
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "" msgstr ""
@@ -138,55 +194,67 @@ msgstr ""
msgid "Generate a ALR script for a pip module" msgid "Generate a ALR script for a pip module"
msgstr "" msgstr ""
#: gen.go:66
msgid "Generate a ALR script for an AUR package"
msgstr ""
#: gen.go:72
msgid "Name of the AUR package"
msgstr ""
#: gen.go:77
msgid "Version of the package (optional, uses latest if not specified)"
msgstr ""
#: helper.go:42 #: helper.go:42
msgid "List all the available helper commands" msgid "List all the available helper commands"
msgstr "" msgstr ""
#: helper.go:54 #: helper.go:69
msgid "Run a ALR helper command" msgid "Run a ALR helper command"
msgstr "" msgstr ""
#: helper.go:61 #: helper.go:76
msgid "The directory that the install commands will install to" msgid "The directory that the install commands will install to"
msgstr "" msgstr ""
#: helper.go:74 helper.go:75 #: helper.go:89 helper.go:90
msgid "No such helper command" msgid "No such helper command"
msgstr "" msgstr ""
#: helper.go:85 #: helper.go:100
msgid "Error parsing os-release file" msgid "Error parsing os-release file"
msgstr "" msgstr ""
#: info.go:42 #: info.go:41
msgid "Print information about a package" msgid "Print information about a package"
msgstr "" msgstr ""
#: info.go:47 #: info.go:46
msgid "Show all information, not just for the current distro" msgid "Show all information, not just for the current distro"
msgstr "" msgstr ""
#: info.go:68 #: info.go:64
msgid "Error getting packages" msgid "Error getting packages"
msgstr "" msgstr ""
#: info.go:83 #: info.go:77
msgid "Command info expected at least 1 argument, got %d" msgid "Command info expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: info.go:104 #: info.go:98
msgid "Error finding packages" msgid "Error finding packages"
msgstr "" msgstr ""
#: info.go:118 #: info.go:112
msgid "Can't detect system language" msgid "Can't detect system language"
msgstr "" msgstr ""
#: info.go:134 #: info.go:128
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "" msgstr ""
#: info.go:143 #: info.go:137
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "" msgstr ""
@@ -198,43 +266,47 @@ msgstr ""
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:113 #: install.go:107
msgid "Error when installing the package" msgid "Error when installing the package"
msgstr "" msgstr ""
#: install.go:151 #: install.go:142
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "" msgstr ""
#: install.go:170 #: install.go:161
msgid "Error listing installed packages" msgid "Error listing installed packages"
msgstr "" msgstr ""
#: install.go:199 #: install.go:190
msgid "Command remove expected at least 1 argument, got %d" msgid "Command remove expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:214 #: install.go:205
msgid "Error removing packages" msgid "Error removing packages"
msgstr "" msgstr ""
#: internal/build/build.go:351 #: internal/build/build.go:342 internal/build/build.go:653
msgid "Using cached package"
msgstr ""
#: internal/build/build.go:357
msgid "Building package" msgid "Building package"
msgstr "" msgstr ""
#: internal/build/build.go:380 #: internal/build/build.go:386
msgid "The checksums array must be the same length as sources" msgid "The checksums array must be the same length as sources"
msgstr "" msgstr ""
#: internal/build/build.go:422 #: internal/build/build.go:438
msgid "Downloading sources" msgid "Downloading sources"
msgstr "" msgstr ""
#: internal/build/build.go:468 #: internal/build/build.go:484
msgid "Would you like to remove the build dependencies?" msgid "Would you like to remove the build dependencies?"
msgstr "" msgstr ""
#: internal/build/build.go:546 #: internal/build/build.go:569
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "" msgstr ""
@@ -272,22 +344,68 @@ msgstr ""
msgid "Applying FireJail integration" msgid "Applying FireJail integration"
msgstr "" msgstr ""
#: internal/build/script_executor.go:145 #: internal/build/installer.go:99
msgid ""
"Package %s is installed with older version %s, will rebuild with version %s"
msgstr ""
#: internal/build/installer.go:102
msgid "Package %s is already installed with version %s, skipping build"
msgstr ""
#: internal/build/installer.go:104
msgid ""
"Package %s is installed with newer version %s (repo has %s), skipping build"
msgstr ""
#: internal/build/script_executor.go:167
msgid "Building package metadata" msgid "Building package metadata"
msgstr "" msgstr ""
#: internal/build/script_executor.go:285 #: internal/build/script_executor.go:192
msgid "Creating package file"
msgstr ""
#: internal/build/script_executor.go:196
msgid "Failed to create package file"
msgstr ""
#: internal/build/script_executor.go:201
msgid "Packaging with nfpm"
msgstr ""
#: internal/build/script_executor.go:204
msgid "Failed to create package"
msgstr ""
#: internal/build/script_executor.go:208
msgid "Package created successfully"
msgstr ""
#: internal/build/script_executor.go:212
msgid "Package file not found after creation"
msgstr ""
#: internal/build/script_executor.go:215
msgid "Package file verified to exist"
msgstr ""
#: internal/build/script_executor.go:322
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "" msgstr ""
#: internal/build/script_executor.go:294 #: internal/build/script_executor.go:331
msgid "Executing build()" msgid "Executing build()"
msgstr "" msgstr ""
#: internal/build/script_executor.go:323 internal/build/script_executor.go:343 #: internal/build/script_executor.go:360 internal/build/script_executor.go:380
msgid "Executing %s()" msgid "Executing %s()"
msgstr "" msgstr ""
#: internal/cliutils/app_builder/builder.go:45
msgid "failed to close db"
msgstr ""
#: internal/cliutils/app_builder/builder.go:75 #: internal/cliutils/app_builder/builder.go:75
msgid "Error loading config" msgid "Error loading config"
msgstr "" msgstr ""
@@ -320,23 +438,25 @@ msgstr ""
msgid "User chose not to continue after reading script" msgid "User chose not to continue after reading script"
msgstr "" msgstr ""
#: internal/cliutils/prompt.go:111 #: internal/cliutils/prompt.go:123
msgid "Error prompting for choice of package" msgid "Error prompting for choice of package"
msgstr "" msgstr ""
#: internal/cliutils/prompt.go:135 #: internal/cliutils/prompt.go:175
msgid "Choose which package to %s" msgid "Choose which package to %s"
msgstr "" msgstr ""
#: internal/cliutils/prompt.go:156 #: internal/cliutils/prompt.go:196
msgid "Choose which optional package(s) to install" msgid "Choose which optional package(s) to install"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:93 #: internal/cliutils/template.go:74 internal/cliutils/template.go:93
#: internal/cliutils/template.go:126
msgid "NAME" msgid "NAME"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:94 #: internal/cliutils/template.go:74 internal/cliutils/template.go:94
#: internal/cliutils/template.go:126
msgid "USAGE" msgid "USAGE"
msgstr "" msgstr ""
@@ -344,15 +464,17 @@ msgstr ""
msgid "global options" msgid "global options"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/cliutils/template.go:74 internal/cliutils/template.go:126
msgid "command" msgid "command"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:95 #: internal/cliutils/template.go:74 internal/cliutils/template.go:95
#: internal/cliutils/template.go:126
msgid "command options" msgid "command options"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:96 #: internal/cliutils/template.go:74 internal/cliutils/template.go:96
#: internal/cliutils/template.go:126
msgid "arguments" msgid "arguments"
msgstr "" msgstr ""
@@ -361,14 +483,15 @@ msgid "VERSION"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:98 #: internal/cliutils/template.go:74 internal/cliutils/template.go:98
#: internal/cliutils/template.go:126
msgid "DESCRIPTION" msgid "DESCRIPTION"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/cliutils/template.go:74 internal/cliutils/template.go:126
msgid "AUTHOR" msgid "AUTHOR"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/cliutils/template.go:74 internal/cliutils/template.go:126
msgid "COMMANDS" msgid "COMMANDS"
msgstr "" msgstr ""
@@ -376,7 +499,7 @@ msgstr ""
msgid "GLOBAL OPTIONS" msgid "GLOBAL OPTIONS"
msgstr "" msgstr ""
#: internal/cliutils/template.go:74 #: internal/cliutils/template.go:74 internal/cliutils/template.go:126
msgid "COPYRIGHT" msgid "COPYRIGHT"
msgstr "" msgstr ""
@@ -385,6 +508,7 @@ msgid "CATEGORY"
msgstr "" msgstr ""
#: internal/cliutils/template.go:99 internal/cliutils/template.go:100 #: internal/cliutils/template.go:99 internal/cliutils/template.go:100
#: internal/cliutils/template.go:126
msgid "OPTIONS" msgid "OPTIONS"
msgstr "" msgstr ""
@@ -394,11 +518,15 @@ msgid ""
"instead!" "instead!"
msgstr "" msgstr ""
#: internal/db/db.go:76 #: internal/db/db.go:67
msgid "Cache directory does not exist, creating it"
msgstr ""
#: internal/db/db.go:95
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "" msgstr ""
#: internal/db/db.go:82 #: internal/db/db.go:101
msgid "" msgid ""
"Database version does not exist. Run alr fix if something isn't working." "Database version does not exist. Run alr fix if something isn't working."
msgstr "" msgstr ""
@@ -433,43 +561,55 @@ msgid ""
"updating ALR if something doesn't work." "updating ALR if something doesn't work."
msgstr "" msgstr ""
#: internal/utils/cmd.go:97 #: internal/repos/pull.go:423
msgid "Error on dropping capabilities" msgid "Failed to get deleted file from old commit"
msgstr "" msgstr ""
#: internal/utils/cmd.go:164 #: internal/repos/pull.go:429
msgid "You need to be a %s member to perform this action" msgid "Failed to read deleted file"
msgstr "" msgstr ""
#: internal/utils/cmd.go:200 #: internal/repos/pull.go:448
msgid "Failed to get updated file from new commit"
msgstr ""
#: internal/repos/pull.go:454
msgid "Failed to read updated file"
msgstr ""
#: internal/repos/pull.go:508
msgid "No alr.sh files found in repository"
msgstr ""
#: internal/utils/cmd.go:54
msgid "You need to be root to perform this action" msgid "You need to be root to perform this action"
msgstr "" msgstr ""
#: list.go:45 #: list.go:44
msgid "List ALR repo packages" msgid "List ALR repo packages"
msgstr "" msgstr ""
#: list.go:59 #: list.go:58
msgid "Format output using a Go template" msgid "Format output using a Go template"
msgstr "" msgstr ""
#: list.go:91 #: list.go:87
msgid "Error getting packages for upgrade" msgid "Error getting packages for upgrade"
msgstr "" msgstr ""
#: list.go:94 #: list.go:90
msgid "No packages for upgrade" msgid "No packages for upgrade"
msgstr "" msgstr ""
#: list.go:104 list.go:201 #: list.go:100 list.go:197
msgid "Error parsing format template" msgid "Error parsing format template"
msgstr "" msgstr ""
#: list.go:110 list.go:205 #: list.go:106 list.go:201
msgid "Error executing template" msgid "Error executing template"
msgstr "" msgstr ""
#: list.go:164 #: list.go:160
msgid "Failed to parse release" msgid "Failed to parse release"
msgstr "" msgstr ""
@@ -477,19 +617,19 @@ msgstr ""
msgid "Print the current ALR version and exit" msgid "Print the current ALR version and exit"
msgstr "" msgstr ""
#: main.go:61 #: main.go:77
msgid "Arguments to be passed on to the package manager" msgid "Arguments to be passed on to the package manager"
msgstr "" msgstr ""
#: main.go:67 #: main.go:83
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "" msgstr ""
#: main.go:148 #: main.go:165
msgid "Show help" msgid "Show help"
msgstr "" msgstr ""
#: main.go:152 #: main.go:169
msgid "Error while running app" msgid "Error while running app"
msgstr "" msgstr ""
@@ -525,124 +665,124 @@ msgstr ""
msgid "Manage repos" msgid "Manage repos"
msgstr "" msgstr ""
#: repo.go:56 repo.go:625 #: repo.go:74 repo.go:658
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "" msgstr ""
#: repo.go:58 repo.go:521 #: repo.go:76 repo.go:554
msgid "<name>" msgid "<name>"
msgstr "" msgstr ""
#: repo.go:103 repo.go:465 repo.go:568 #: repo.go:121 repo.go:498 repo.go:601
msgid "Repo \"%s\" does not exist" msgid "Repo \"%s\" does not exist"
msgstr "" msgstr ""
#: repo.go:110 #: repo.go:128
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "" msgstr ""
#: repo.go:114 repo.go:195 repo.go:253 repo.go:316 repo.go:389 repo.go:504 #: repo.go:132 repo.go:210 repo.go:268 repo.go:331 repo.go:422 repo.go:537
#: repo.go:576 #: repo.go:609
msgid "Error saving config" msgid "Error saving config"
msgstr "" msgstr ""
#: repo.go:133 #: repo.go:148
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "" msgstr ""
#: repo.go:144 repo.go:595 #: repo.go:159 repo.go:628
msgid "Add a new repository" msgid "Add a new repository"
msgstr "" msgstr ""
#: repo.go:145 repo.go:270 repo.go:345 repo.go:402 #: repo.go:160 repo.go:285 repo.go:378 repo.go:435
msgid "<name> <url>" msgid "<name> <url>"
msgstr "" msgstr ""
#: repo.go:170 #: repo.go:185
msgid "Repo \"%s\" already exists" msgid "Repo \"%s\" already exists"
msgstr "" msgstr ""
#: repo.go:206 #: repo.go:221
msgid "Set the reference of the repository" msgid "Set the reference of the repository"
msgstr "" msgstr ""
#: repo.go:207 #: repo.go:222
msgid "<name> <ref>" msgid "<name> <ref>"
msgstr "" msgstr ""
#: repo.go:269 #: repo.go:284
msgid "Set the main url of the repository" msgid "Set the main url of the repository"
msgstr "" msgstr ""
#: repo.go:332 #: repo.go:347
msgid "Manage mirrors of repos" msgid "Manage mirrors of repos"
msgstr "" msgstr ""
#: repo.go:344 #: repo.go:377
msgid "Add a mirror URL to repository" msgid "Add a mirror URL to repository"
msgstr "" msgstr ""
#: repo.go:401 #: repo.go:434
msgid "Remove mirror from the repository" msgid "Remove mirror from the repository"
msgstr "" msgstr ""
#: repo.go:420 #: repo.go:453
msgid "Ignore if mirror does not exist" msgid "Ignore if mirror does not exist"
msgstr "" msgstr ""
#: repo.go:425 #: repo.go:458
msgid "Match partial URL (e.g., github.com instead of full URL)" msgid "Match partial URL (e.g., github.com instead of full URL)"
msgstr "" msgstr ""
#: repo.go:490 #: repo.go:523
msgid "No mirrors containing \"%s\" found in repo \"%s\"" msgid "No mirrors containing \"%s\" found in repo \"%s\""
msgstr "" msgstr ""
#: repo.go:492 #: repo.go:525
msgid "URL \"%s\" does not exist in repo \"%s\"" msgid "URL \"%s\" does not exist in repo \"%s\""
msgstr "" msgstr ""
#: repo.go:508 repo.go:580 #: repo.go:541 repo.go:613
msgid "Removed %d mirrors from repo \"%s\"\n" msgid "Removed %d mirrors from repo \"%s\"\n"
msgstr "" msgstr ""
#: repo.go:520 #: repo.go:553
msgid "Remove all mirrors from the repository" msgid "Remove all mirrors from the repository"
msgstr "" msgstr ""
#: repo.go:602 #: repo.go:635
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "" msgstr ""
#: repo.go:608 #: repo.go:641
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "" msgstr ""
#: repo.go:632 #: repo.go:665
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "" msgstr ""
#: search.go:40 #: search.go:39
msgid "Search packages" msgid "Search packages"
msgstr "" msgstr ""
#: search.go:51 #: search.go:50
msgid "Search by name" msgid "Search by name"
msgstr "" msgstr ""
#: search.go:56 #: search.go:55
msgid "Search by description" msgid "Search by description"
msgstr "" msgstr ""
#: search.go:61 #: search.go:60
msgid "Search by repository" msgid "Search by repository"
msgstr "" msgstr ""
#: search.go:66 #: search.go:65
msgid "Search by provides" msgid "Search by provides"
msgstr "" msgstr ""
#: search.go:130 #: search.go:126
msgid "Error while executing search" msgid "Error while executing search"
msgstr "" msgstr ""
@@ -650,10 +790,22 @@ msgstr ""
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "" msgstr ""
#: upgrade.go:106 upgrade.go:123 #: upgrade.go:89
msgid "Updating system packages..."
msgstr ""
#: upgrade.go:95
msgid "Error updating system packages"
msgstr ""
#: upgrade.go:97
msgid "System packages updated successfully"
msgstr ""
#: upgrade.go:113 upgrade.go:130
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "" msgstr ""
#: upgrade.go:126 #: upgrade.go:133
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "" msgstr ""

View File

@@ -41,27 +41,31 @@ msgstr "Создайте пакет с нуля, даже если уже име
msgid "Error getting working directory" msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога" msgstr "Ошибка при получении рабочего каталога"
#: build.go:117 #: build.go:111
msgid "Cannot get absolute script path" msgid "Cannot get absolute script path"
msgstr "Невозможно получить абсолютный путь к скрипту" msgstr "Невозможно получить абсолютный путь к скрипту"
#: build.go:143 #: build.go:137
msgid "Package not found" msgid "Package not found"
msgstr "Пакет не найден" msgstr "Пакет не найден"
#: build.go:156 #: build.go:150
msgid "Nothing to build" msgid "Nothing to build"
msgstr "Нечего собирать" msgstr "Нечего собирать"
#: build.go:213 #: build.go:195
msgid "Error building package" msgid "Error building package"
msgstr "Ошибка при сборке пакета" msgstr "Ошибка при сборке пакета"
#: build.go:220 #: build.go:203
msgid "Package file already moved or removed, skipping"
msgstr "Файл пакета уже перемещён или удалён, пропускаем"
#: build.go:209
msgid "Error moving the package" msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета" msgstr "Ошибка при перемещении пакета"
#: build.go:224 #: build.go:213
msgid "Done" msgid "Done"
msgstr "Сделано" msgstr "Сделано"
@@ -69,71 +73,132 @@ msgstr "Сделано"
msgid "Manage config" msgid "Manage config"
msgstr "Управление конфигурацией" msgstr "Управление конфигурацией"
#: config.go:48 #: config.go:50
msgid "Shows a list of commands or help for one command"
msgstr "Показывает список команд или справку по одной команде"
#: config.go:66
msgid "Show config" msgid "Show config"
msgstr "Показать конфигурацию" msgstr "Показать конфигурацию"
#: config.go:84 #: config.go:103
msgid "Set config value" msgid "Set config value"
msgstr "Установить значение в конфигурации" msgstr "Установить значение в конфигурации"
#: config.go:85 #: config.go:104
msgid "<key> <value>" msgid "<key> <value>"
msgstr "<ключ> <значение>" msgstr "<ключ> <значение>"
#: config.go:118 config.go:126 #: config.go:137 config.go:145 config.go:162
msgid "invalid boolean value for %s: %s" msgid "invalid boolean value for %s: %s"
msgstr "неверное булево значение для %s: %s" msgstr "неверное булево значение для %s: %s"
#: config.go:141 #: config.go:166
msgid "use 'repo add/remove' commands to manage repositories" msgid "use 'repo add/remove' commands to manage repositories"
msgstr "используйте команды 'repo add/remove' для управления репозиториями" msgstr "используйте команды 'repo add/remove' для управления репозиториями"
#: config.go:143 config.go:221 #: config.go:168 config.go:248
msgid "unknown config key: %s" msgid "unknown config key: %s"
msgstr "неизвестный ключ конфигурации: %s" msgstr "неизвестный ключ конфигурации: %s"
#: config.go:147 #: config.go:172
msgid "failed to save config" msgid "failed to save config"
msgstr "не удалось сохранить конфигурацию" msgstr "не удалось сохранить конфигурацию"
#: config.go:150 #: config.go:175
msgid "Successfully set %s = %s" msgid "Successfully set %s = %s"
msgstr "Успешно установлено %s = %s" msgstr "Успешно установлено %s = %s"
#: config.go:159 #: config.go:184
msgid "Get config value" msgid "Get config value"
msgstr "Получить значение из конфигурации" msgstr "Получить значение из конфигурации"
#: config.go:160 #: config.go:185
msgid "<key>" msgid "<key>"
msgstr "<ключ>" msgstr "<ключ>"
#: fix.go:39 #: fix.go:55
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR" msgstr "Попытка устранить проблемы с ALR"
#: fix.go:60 #: fix.go:75
msgid "Clearing cache directory" msgid "Clearing cache and temporary directories"
msgstr "Очистка каталога кэша" msgstr "Очистка кэша и временных директорий"
#: fix.go:64 #: fix.go:82
msgid "Cache directory does not exist, will create it"
msgstr ""
#: fix.go:84
msgid "Unable to open cache directory" msgid "Unable to open cache directory"
msgstr "Невозможно открыть каталог кэша" msgstr "Невозможно открыть каталог кэша"
#: fix.go:70 #: fix.go:91
msgid "Unable to read cache directory contents" msgid "Unable to read cache directory contents"
msgstr "Невозможно прочитать содержимое каталога кэша" msgstr "Невозможно прочитать содержимое каталога кэша"
#: fix.go:82 #: fix.go:106
#, fuzzy
msgid "Unable to remove cache item (%s) as current user, trying with sudo"
msgstr ""
"Невозможно удалить временную директорию от текущего пользователя, попытка "
"через sudo"
#: fix.go:111
msgid "Unable to remove cache item (%s)" msgid "Unable to remove cache item (%s)"
msgstr "Невозможно удалить элемент кэша (%s)" msgstr "Невозможно удалить элемент кэша (%s)"
#: fix.go:86 #: fix.go:119
msgid "Clearing temporary directory"
msgstr "Очистка временной директории"
#: fix.go:126
msgid "Unable to remove temporary directory as current user, trying with sudo"
msgstr ""
"Невозможно удалить временную директорию от текущего пользователя, попытка "
"через sudo"
#: fix.go:129
#, fuzzy
msgid "Unable to remove temporary directory"
msgstr "Невозможно открыть каталог кэша"
#: fix.go:137
#, fuzzy
msgid "Unable to create temporary directory"
msgstr "Не удалось создать каталог конфигурации ALR"
#: fix.go:144
#, fuzzy
msgid "Unable to create download directory"
msgstr "Не удалось создать каталог конфигурации ALR"
#: fix.go:151
#, fuzzy
msgid "Unable to create packages directory"
msgstr "Не удалось создать каталог кэша пакетов"
#: fix.go:156
msgid "Fixing permissions on temporary files"
msgstr "Исправление прав доступа к временным файлам"
#: fix.go:164
msgid "Unable to fix file ownership"
msgstr ""
#: fix.go:169
msgid "Unable to fix file permissions"
msgstr ""
#: fix.go:174
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "Восстановление кэша" msgstr "Восстановление кэша"
#: fix.go:90 #: fix.go:177
msgid "Creating cache directory"
msgstr "Создание директории кэша"
#: fix.go:180
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "Не удалось создать новый каталог кэша" msgstr "Не удалось создать новый каталог кэша"
@@ -145,55 +210,69 @@ msgstr "Генерация скрипта ALR из шаблона"
msgid "Generate a ALR script for a pip module" msgid "Generate a ALR script for a pip module"
msgstr "Генерация скрипта ALR для модуля pip" msgstr "Генерация скрипта ALR для модуля pip"
#: gen.go:66
#, fuzzy
msgid "Generate a ALR script for an AUR package"
msgstr "Генерация скрипта ALR из шаблона"
#: gen.go:72
#, fuzzy
msgid "Name of the AUR package"
msgstr "Название нового репозитория"
#: gen.go:77
msgid "Version of the package (optional, uses latest if not specified)"
msgstr ""
#: helper.go:42 #: helper.go:42
msgid "List all the available helper commands" msgid "List all the available helper commands"
msgstr "Список всех доступных вспомогательных команды" msgstr "Список всех доступных вспомогательных команды"
#: helper.go:54 #: helper.go:69
msgid "Run a ALR helper command" msgid "Run a ALR helper command"
msgstr "Запустить вспомогательную команду ALR" msgstr "Запустить вспомогательную команду ALR"
#: helper.go:61 #: helper.go:76
msgid "The directory that the install commands will install to" msgid "The directory that the install commands will install to"
msgstr "Каталог, в который будут устанавливать команды установки" msgstr "Каталог, в который будут устанавливать команды установки"
#: helper.go:74 helper.go:75 #: helper.go:89 helper.go:90
msgid "No such helper command" msgid "No such helper command"
msgstr "Такой вспомогательной команды нет" msgstr "Такой вспомогательной команды нет"
#: helper.go:85 #: helper.go:100
msgid "Error parsing os-release file" msgid "Error parsing os-release file"
msgstr "Ошибка при разборе файла выпуска операционной системы" msgstr "Ошибка при разборе файла выпуска операционной системы"
#: info.go:42 #: info.go:41
msgid "Print information about a package" msgid "Print information about a package"
msgstr "Отобразить информацию о пакете" msgstr "Отобразить информацию о пакете"
#: info.go:47 #: info.go:46
msgid "Show all information, not just for the current distro" msgid "Show all information, not just for the current distro"
msgstr "Показывать всю информацию, не только для текущего дистрибутива" msgstr "Показывать всю информацию, не только для текущего дистрибутива"
#: info.go:68 #: info.go:64
msgid "Error getting packages" msgid "Error getting packages"
msgstr "Ошибка при получении пакетов" msgstr "Ошибка при получении пакетов"
#: info.go:83 #: info.go:77
msgid "Command info expected at least 1 argument, got %d" msgid "Command info expected at least 1 argument, got %d"
msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d"
#: info.go:104 #: info.go:98
msgid "Error finding packages" msgid "Error finding packages"
msgstr "Ошибка при поиске пакетов" msgstr "Ошибка при поиске пакетов"
#: info.go:118 #: info.go:112
msgid "Can't detect system language" msgid "Can't detect system language"
msgstr "Ошибка при определении языка системы" msgstr "Ошибка при определении языка системы"
#: info.go:134 #: info.go:128
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "Ошибка устранения переорпеделений" msgstr "Ошибка устранения переорпеделений"
#: info.go:143 #: info.go:137
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита" msgstr "Ошибка кодирования переменных скрита"
@@ -205,43 +284,47 @@ msgstr "Установить новый пакет"
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
#: install.go:113 #: install.go:107
msgid "Error when installing the package" msgid "Error when installing the package"
msgstr "Ошибка при установке пакета" msgstr "Ошибка при установке пакета"
#: install.go:151 #: install.go:142
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "Удалить установленный пакет" msgstr "Удалить установленный пакет"
#: install.go:170 #: install.go:161
msgid "Error listing installed packages" msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов" msgstr "Ошибка при составлении списка установленных пакетов"
#: install.go:199 #: install.go:190
msgid "Command remove expected at least 1 argument, got %d" msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
#: install.go:214 #: install.go:205
msgid "Error removing packages" msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов" msgstr "Ошибка при удалении пакетов"
#: internal/build/build.go:351 #: internal/build/build.go:342 internal/build/build.go:653
msgid "Using cached package"
msgstr "Используется кешированный пакет"
#: internal/build/build.go:357
msgid "Building package" msgid "Building package"
msgstr "Сборка пакета" msgstr "Сборка пакета"
#: internal/build/build.go:380 #: internal/build/build.go:386
msgid "The checksums array must be the same length as sources" msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники" msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
#: internal/build/build.go:422 #: internal/build/build.go:438
msgid "Downloading sources" msgid "Downloading sources"
msgstr "Скачивание источников" msgstr "Скачивание источников"
#: internal/build/build.go:468 #: internal/build/build.go:484
msgid "Would you like to remove the build dependencies?" msgid "Would you like to remove the build dependencies?"
msgstr "Хотели бы вы удалить зависимости сборки?" msgstr "Хотели бы вы удалить зависимости сборки?"
#: internal/build/build.go:546 #: internal/build/build.go:569
msgid "Installing dependencies" msgid "Installing dependencies"
msgstr "Установка зависимостей" msgstr "Установка зависимостей"
@@ -283,22 +366,71 @@ msgstr ""
msgid "Applying FireJail integration" msgid "Applying FireJail integration"
msgstr "Применение интеграции FireJail" msgstr "Применение интеграции FireJail"
#: internal/build/script_executor.go:145 #: internal/build/installer.go:99
msgid ""
"Package %s is installed with older version %s, will rebuild with version %s"
msgstr ""
"Пакет %s установлен с устаревшей версией %s, будет пересобран с версией %s"
#: internal/build/installer.go:102
msgid "Package %s is already installed with version %s, skipping build"
msgstr "Пакет %s уже установлен с версией %s, пропуск сборки"
#: internal/build/installer.go:104
msgid ""
"Package %s is installed with newer version %s (repo has %s), skipping build"
msgstr ""
"Пакет %s установлен с более новой версией %s (в репозитории %s), пропуск "
"сборки"
#: internal/build/script_executor.go:167
msgid "Building package metadata" msgid "Building package metadata"
msgstr "Сборка метаданных пакета" msgstr "Сборка метаданных пакета"
#: internal/build/script_executor.go:285 #: internal/build/script_executor.go:192
msgid "Creating package file"
msgstr "Создание файла пакета"
#: internal/build/script_executor.go:196
msgid "Failed to create package file"
msgstr "Не удалось создать файл пакета"
#: internal/build/script_executor.go:201
msgid "Packaging with nfpm"
msgstr "Упаковка с помощью nfpm"
#: internal/build/script_executor.go:204
msgid "Failed to create package"
msgstr "Не удалось создать пакет"
#: internal/build/script_executor.go:208
msgid "Package created successfully"
msgstr "Пакет успешно создан"
#: internal/build/script_executor.go:212
msgid "Package file not found after creation"
msgstr "Файл пакета не найден после создания"
#: internal/build/script_executor.go:215
msgid "Package file verified to exist"
msgstr "Наличие файла пакета подтверждено"
#: internal/build/script_executor.go:322
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "Выполнение prepare()" msgstr "Выполнение prepare()"
#: internal/build/script_executor.go:294 #: internal/build/script_executor.go:331
msgid "Executing build()" msgid "Executing build()"
msgstr "Выполнение build()" msgstr "Выполнение build()"
#: internal/build/script_executor.go:323 internal/build/script_executor.go:343 #: internal/build/script_executor.go:360 internal/build/script_executor.go:380
msgid "Executing %s()" msgid "Executing %s()"
msgstr "Выполнение %s()" msgstr "Выполнение %s()"
#: internal/cliutils/app_builder/builder.go:45
msgid "failed to close db"
msgstr "не удалось закрыть БД"
#: internal/cliutils/app_builder/builder.go:75 #: internal/cliutils/app_builder/builder.go:75
msgid "Error loading config" msgid "Error loading config"
msgstr "Ошибка при загрузке" msgstr "Ошибка при загрузке"
@@ -331,23 +463,25 @@ msgstr "Продолжить?"
msgid "User chose not to continue after reading script" msgid "User chose not to continue after reading script"
msgstr "Пользователь решил не продолжать после просмотра скрипта" msgstr "Пользователь решил не продолжать после просмотра скрипта"
#: internal/cliutils/prompt.go:111 #: internal/cliutils/prompt.go:123
msgid "Error prompting for choice of package" msgid "Error prompting for choice of package"
msgstr "Ошибка при запросе выбора пакета" msgstr "Ошибка при запросе выбора пакета"
#: internal/cliutils/prompt.go:135 #: internal/cliutils/prompt.go:175
msgid "Choose which package to %s" msgid "Choose which package to %s"
msgstr "Выберите, какой пакет использовать для %s" msgstr "Выберите, какой пакет использовать для %s"
#: internal/cliutils/prompt.go:156 #: internal/cliutils/prompt.go:196
msgid "Choose which optional package(s) to install" msgid "Choose which optional package(s) to install"
msgstr "Выберите, какой дополнительный пакет(ы) следует установить" msgstr "Выберите дополнительные пакеты для установки"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:93 #: internal/cliutils/template.go:74 internal/cliutils/template.go:93
#: internal/cliutils/template.go:126
msgid "NAME" msgid "NAME"
msgstr "НАЗВАНИЕ" msgstr "НАЗВАНИЕ"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:94 #: internal/cliutils/template.go:74 internal/cliutils/template.go:94
#: internal/cliutils/template.go:126
msgid "USAGE" msgid "USAGE"
msgstr "ИСПОЛЬЗОВАНИЕ" msgstr "ИСПОЛЬЗОВАНИЕ"
@@ -355,15 +489,17 @@ msgstr "ИСПОЛЬЗОВАНИЕ"
msgid "global options" msgid "global options"
msgstr "глобальные опции" msgstr "глобальные опции"
#: internal/cliutils/template.go:74 #: internal/cliutils/template.go:74 internal/cliutils/template.go:126
msgid "command" msgid "command"
msgstr "команда" msgstr "команда"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:95 #: internal/cliutils/template.go:74 internal/cliutils/template.go:95
#: internal/cliutils/template.go:126
msgid "command options" msgid "command options"
msgstr "опции команды" msgstr "опции команды"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:96 #: internal/cliutils/template.go:74 internal/cliutils/template.go:96
#: internal/cliutils/template.go:126
msgid "arguments" msgid "arguments"
msgstr "аргументы" msgstr "аргументы"
@@ -372,14 +508,15 @@ msgid "VERSION"
msgstr "ВЕРСИЯ" msgstr "ВЕРСИЯ"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:98 #: internal/cliutils/template.go:74 internal/cliutils/template.go:98
#: internal/cliutils/template.go:126
msgid "DESCRIPTION" msgid "DESCRIPTION"
msgstr "ОПИСАНИЕ" msgstr "ОПИСАНИЕ"
#: internal/cliutils/template.go:74 #: internal/cliutils/template.go:74 internal/cliutils/template.go:126
msgid "AUTHOR" msgid "AUTHOR"
msgstr "АВТОР" msgstr "АВТОР"
#: internal/cliutils/template.go:74 #: internal/cliutils/template.go:74 internal/cliutils/template.go:126
msgid "COMMANDS" msgid "COMMANDS"
msgstr "КОМАНДЫ" msgstr "КОМАНДЫ"
@@ -387,7 +524,7 @@ msgstr "КОМАНДЫ"
msgid "GLOBAL OPTIONS" msgid "GLOBAL OPTIONS"
msgstr "ГЛОБАЛЬНЫЕ ОПЦИИ" msgstr "ГЛОБАЛЬНЫЕ ОПЦИИ"
#: internal/cliutils/template.go:74 #: internal/cliutils/template.go:74 internal/cliutils/template.go:126
msgid "COPYRIGHT" msgid "COPYRIGHT"
msgstr "АВТОРСКОЕ ПРАВО" msgstr "АВТОРСКОЕ ПРАВО"
@@ -396,6 +533,7 @@ msgid "CATEGORY"
msgstr "КАТЕГОРИЯ" msgstr "КАТЕГОРИЯ"
#: internal/cliutils/template.go:99 internal/cliutils/template.go:100 #: internal/cliutils/template.go:99 internal/cliutils/template.go:100
#: internal/cliutils/template.go:126
msgid "OPTIONS" msgid "OPTIONS"
msgstr "ПАРАМЕТРЫ" msgstr "ПАРАМЕТРЫ"
@@ -407,11 +545,15 @@ msgstr ""
"Эта команда устарела и будет удалена в будущем, используйте вместо нее " "Эта команда устарела и будет удалена в будущем, используйте вместо нее "
"\"%s\"!" "\"%s\"!"
#: internal/db/db.go:76 #: internal/db/db.go:67
msgid "Cache directory does not exist, creating it"
msgstr ""
#: internal/db/db.go:95
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "Несоответствие версий базы данных; сброс настроек" msgstr "Несоответствие версий базы данных; сброс настроек"
#: internal/db/db.go:82 #: internal/db/db.go:101
msgid "" msgid ""
"Database version does not exist. Run alr fix if something isn't working." "Database version does not exist. Run alr fix if something isn't working."
msgstr "" msgstr ""
@@ -449,43 +591,55 @@ msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте " "Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает." "обновить ALR, если что-то не работает."
#: internal/utils/cmd.go:97 #: internal/repos/pull.go:423
msgid "Error on dropping capabilities" msgid "Failed to get deleted file from old commit"
msgstr "Ошибка при понижении привилегий" msgstr "Не удалось получить удалённый файл из старого коммита"
#: internal/utils/cmd.go:164 #: internal/repos/pull.go:429
msgid "You need to be a %s member to perform this action" msgid "Failed to read deleted file"
msgstr "Вы должны быть членом %s чтобы выполнить это" msgstr "Не удалось прочитать удалённый файл"
#: internal/utils/cmd.go:200 #: internal/repos/pull.go:448
msgid "Failed to get updated file from new commit"
msgstr "Не удалось получить обновлённый файл из нового коммита"
#: internal/repos/pull.go:454
msgid "Failed to read updated file"
msgstr "Не удалось прочитать обновлённый файл"
#: internal/repos/pull.go:508
msgid "No alr.sh files found in repository"
msgstr "Файлы alr.sh не найдены в репозитории"
#: internal/utils/cmd.go:54
msgid "You need to be root to perform this action" msgid "You need to be root to perform this action"
msgstr "Вы должны быть root чтобы выполнить это" msgstr "Вы должны быть root чтобы выполнить это"
#: list.go:45 #: list.go:44
msgid "List ALR repo packages" msgid "List ALR repo packages"
msgstr "Список пакетов репозитория ALR" msgstr "Список пакетов репозитория ALR"
#: list.go:59 #: list.go:58
msgid "Format output using a Go template" msgid "Format output using a Go template"
msgstr "Формат выходных данных с использованием шаблона Go" msgstr "Формат выходных данных с использованием шаблона Go"
#: list.go:91 #: list.go:87
msgid "Error getting packages for upgrade" msgid "Error getting packages for upgrade"
msgstr "Ошибка при получении пакетов для обновления" msgstr "Ошибка при получении пакетов для обновления"
#: list.go:94 #: list.go:90
msgid "No packages for upgrade" msgid "No packages for upgrade"
msgstr "Нет пакетов к обновлению" msgstr "Нет пакетов к обновлению"
#: list.go:104 list.go:201 #: list.go:100 list.go:197
msgid "Error parsing format template" msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона" msgstr "Ошибка при разборе шаблона"
#: list.go:110 list.go:205 #: list.go:106 list.go:201
msgid "Error executing template" msgid "Error executing template"
msgstr "Ошибка при выполнении шаблона" msgstr "Ошибка при выполнении шаблона"
#: list.go:164 #: list.go:160
msgid "Failed to parse release" msgid "Failed to parse release"
msgstr "Не удалось разобрать релиз" msgstr "Не удалось разобрать релиз"
@@ -493,19 +647,19 @@ msgstr "Не удалось разобрать релиз"
msgid "Print the current ALR version and exit" msgid "Print the current ALR version and exit"
msgstr "Показать текущую версию ALR и выйти" msgstr "Показать текущую версию ALR и выйти"
#: main.go:61 #: main.go:77
msgid "Arguments to be passed on to the package manager" msgid "Arguments to be passed on to the package manager"
msgstr "Аргументы, которые будут переданы менеджеру пакетов" msgstr "Аргументы, которые будут переданы менеджеру пакетов"
#: main.go:67 #: main.go:83
msgid "Enable interactive questions and prompts" msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов" msgstr "Включение интерактивных вопросов и запросов"
#: main.go:148 #: main.go:165
msgid "Show help" msgid "Show help"
msgstr "Показать справку" msgstr "Показать справку"
#: main.go:152 #: main.go:169
msgid "Error while running app" msgid "Error while running app"
msgstr "Ошибка при запуске приложения" msgstr "Ошибка при запуске приложения"
@@ -541,124 +695,124 @@ msgstr "Скачать все изменённые репозитории"
msgid "Manage repos" msgid "Manage repos"
msgstr "Управление репозиториями" msgstr "Управление репозиториями"
#: repo.go:56 repo.go:625 #: repo.go:74 repo.go:658
msgid "Remove an existing repository" msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий" msgstr "Удалить существующий репозиторий"
#: repo.go:58 repo.go:521 #: repo.go:76 repo.go:554
msgid "<name>" msgid "<name>"
msgstr "<имя>" msgstr "<имя>"
#: repo.go:103 repo.go:465 repo.go:568 #: repo.go:121 repo.go:498 repo.go:601
msgid "Repo \"%s\" does not exist" msgid "Repo \"%s\" does not exist"
msgstr "Репозитория \"%s\" не существует" msgstr "Репозиторий \"%s\" не существует"
#: repo.go:110 #: repo.go:128
msgid "Error removing repo directory" msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория" msgstr "Ошибка при удалении каталога репозитория"
#: repo.go:114 repo.go:195 repo.go:253 repo.go:316 repo.go:389 repo.go:504 #: repo.go:132 repo.go:210 repo.go:268 repo.go:331 repo.go:422 repo.go:537
#: repo.go:576 #: repo.go:609
msgid "Error saving config" msgid "Error saving config"
msgstr "Ошибка при сохранении конфигурации" msgstr "Ошибка при сохранении конфигурации"
#: repo.go:133 #: repo.go:148
msgid "Error removing packages from database" msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных" msgstr "Ошибка при удалении пакетов из базы данных"
#: repo.go:144 repo.go:595 #: repo.go:159 repo.go:628
msgid "Add a new repository" msgid "Add a new repository"
msgstr "Добавить новый репозиторий" msgstr "Добавить новый репозиторий"
#: repo.go:145 repo.go:270 repo.go:345 repo.go:402 #: repo.go:160 repo.go:285 repo.go:378 repo.go:435
msgid "<name> <url>" msgid "<name> <url>"
msgstr "<имя> <url>" msgstr "<имя> <url>"
#: repo.go:170 #: repo.go:185
msgid "Repo \"%s\" already exists" msgid "Repo \"%s\" already exists"
msgstr "Репозиторий \"%s\" уже существует" msgstr "Репозиторий \"%s\" уже существует"
#: repo.go:206 #: repo.go:221
msgid "Set the reference of the repository" msgid "Set the reference of the repository"
msgstr "Установить ссылку на версию репозитория" msgstr "Установить ссылку на версию репозитория"
#: repo.go:207 #: repo.go:222
msgid "<name> <ref>" msgid "<name> <ref>"
msgstr "<имя> <ссылкааерсию>" msgstr "<имя> <ссылкааерсию>"
#: repo.go:269 #: repo.go:284
msgid "Set the main url of the repository" msgid "Set the main url of the repository"
msgstr "Установить главный URL репозитория" msgstr "Установить главный URL репозитория"
#: repo.go:332 #: repo.go:347
msgid "Manage mirrors of repos" msgid "Manage mirrors of repos"
msgstr "Управление зеркалами репозитория" msgstr "Управление зеркалами репозитория"
#: repo.go:344 #: repo.go:377
msgid "Add a mirror URL to repository" msgid "Add a mirror URL to repository"
msgstr "Добавить зеркало репозитория" msgstr "Добавить зеркало репозитория"
#: repo.go:401 #: repo.go:434
msgid "Remove mirror from the repository" msgid "Remove mirror from the repository"
msgstr "Удалить зеркало из репозитория" msgstr "Удалить зеркало из репозитория"
#: repo.go:420 #: repo.go:453
msgid "Ignore if mirror does not exist" msgid "Ignore if mirror does not exist"
msgstr "Игнорировать, если зеркала не существует" msgstr "Игнорировать, если зеркала не существует"
#: repo.go:425 #: repo.go:458
msgid "Match partial URL (e.g., github.com instead of full URL)" msgid "Match partial URL (e.g., github.com instead of full URL)"
msgstr "Соответствует частичному URL (например, github.com вместо полного URL)" msgstr "Соответствует частичному URL (например, github.com вместо полного URL)"
#: repo.go:490 #: repo.go:523
msgid "No mirrors containing \"%s\" found in repo \"%s\"" msgid "No mirrors containing \"%s\" found in repo \"%s\""
msgstr "В репозитории \"%s\" не найдено зеркал, содержащих \"%s\"" msgstr "В репозитории \"%s\" не найдено зеркал, содержащих \"%s\""
#: repo.go:492 #: repo.go:525
msgid "URL \"%s\" does not exist in repo \"%s\"" msgid "URL \"%s\" does not exist in repo \"%s\""
msgstr "URL \"%s\" не существует в репозитории \"%s\"" msgstr "URL \"%s\" не существует в репозитории \"%s\""
#: repo.go:508 repo.go:580 #: repo.go:541 repo.go:613
msgid "Removed %d mirrors from repo \"%s\"\n" msgid "Removed %d mirrors from repo \"%s\"\n"
msgstr "Удалены зеркала %d из репозитория \"%s\"\n" msgstr "Удалено %d зеркал из репозитория \"%s\"\n"
#: repo.go:520 #: repo.go:553
msgid "Remove all mirrors from the repository" msgid "Remove all mirrors from the repository"
msgstr "Удалить все зеркала из репозитория" msgstr "Удалить все зеркала из репозитория"
#: repo.go:602 #: repo.go:635
msgid "Name of the new repo" msgid "Name of the new repo"
msgstr "Название нового репозитория" msgstr "Название нового репозитория"
#: repo.go:608 #: repo.go:641
msgid "URL of the new repo" msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория" msgstr "URL-адрес нового репозитория"
#: repo.go:632 #: repo.go:665
msgid "Name of the repo to be deleted" msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён" msgstr "Название репозитория для удаления"
#: search.go:40 #: search.go:39
msgid "Search packages" msgid "Search packages"
msgstr "Поиск пакетов" msgstr "Поиск пакетов"
#: search.go:51 #: search.go:50
msgid "Search by name" msgid "Search by name"
msgstr "Искать по имени" msgstr "Искать по имени"
#: search.go:56 #: search.go:55
msgid "Search by description" msgid "Search by description"
msgstr "Искать по описанию" msgstr "Искать по описанию"
#: search.go:61 #: search.go:60
msgid "Search by repository" msgid "Search by repository"
msgstr "Искать по репозиторию" msgstr "Искать по репозиторию"
#: search.go:66 #: search.go:65
msgid "Search by provides" msgid "Search by provides"
msgstr "Иcкать по provides" msgstr "Искать по provides"
#: search.go:130 #: search.go:126
msgid "Error while executing search" msgid "Error while executing search"
msgstr "Ошибка при выполнении поиска" msgstr "Ошибка при выполнении поиска"
@@ -666,13 +820,35 @@ msgstr "Ошибка при выполнении поиска"
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты" msgstr "Обновить все установленные пакеты"
#: upgrade.go:106 upgrade.go:123 #: upgrade.go:89
msgid "Updating system packages..."
msgstr "Обновление системных пакетов..."
#: upgrade.go:95
#, fuzzy
msgid "Error updating system packages"
msgstr "Обновление системных пакетов..."
#: upgrade.go:97
msgid "System packages updated successfully"
msgstr "Системные пакеты успешно обновлены"
#: upgrade.go:113 upgrade.go:130
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений" msgstr "Ошибка при проверке обновлений"
#: upgrade.go:126 #: upgrade.go:133
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "Здесь нечего делать." msgstr "Действия не требуются."
#~ msgid "Clearing cache directory"
#~ msgstr "Очистка каталога кэша"
#~ msgid "Error on dropping capabilities"
#~ msgstr "Ошибка при понижении привилегий"
#~ msgid "You need to be a %s member to perform this action"
#~ msgstr "Вы должны быть членом %s чтобы выполнить это"
#, fuzzy #, fuzzy
#~ msgid "Failed to clear contents of cache directory" #~ msgid "Failed to clear contents of cache directory"
@@ -692,13 +868,6 @@ msgstr "Здесь нечего делать."
#~ msgid "Error mounting" #~ msgid "Error mounting"
#~ msgstr "Ошибка при кодировании конфигурации" #~ msgstr "Ошибка при кодировании конфигурации"
#, fuzzy
#~ msgid "Unable to create config directory"
#~ msgstr "Не удалось создать каталог конфигурации ALR"
#~ msgid "Unable to create package cache directory"
#~ msgstr "Не удалось создать каталог кэша пакетов"
#~ msgid "" #~ msgid ""
#~ "Running ALR as root is forbidden as it may cause catastrophic damage to " #~ "Running ALR as root is forbidden as it may cause catastrophic damage to "
#~ "your system" #~ "your system"

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

@@ -17,10 +17,11 @@
package utils package utils
import ( import (
"fmt"
"os" "os"
"os/exec" "os/user"
"strings"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/fsutils"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@@ -28,66 +29,71 @@ 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 // Обёртка для обратной совместимости, делегирует вызов в fsutils
// Для других каталогов использует стандартные права
func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error { func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error {
if strings.HasPrefix(path, "/tmp/alr") { return fsutils.EnsureTempDirWithRootOwner(path, mode)
// Сначала создаем директорию обычным способом }
err := os.MkdirAll(path, mode)
if err != nil { // GetPrivilegedGroup возвращает привилегированную группу для текущей системы
return err // Обёртка для обратной совместимости, делегирует вызов в fsutils
func GetPrivilegedGroup() string {
return fsutils.GetPrivilegedGroup()
}
// 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
} }
}
// В CI или если мы уже root, не нужно использовать sudo return false
isRoot := os.Geteuid() == 0 }
isCI := os.Getenv("CI") == "true"
// CheckUserPrivileges проверяет, что пользователь имеет необходимые привилегии для работы с ALR
// В CI создаем директории с обычными правами // Пользователь должен быть root или состоять в группе wheel/sudo
if isCI { func CheckUserPrivileges() error {
// В CI не используем группу wheel и не меняем права // Если пользователь root - все в порядке
// Устанавливаем базовые права 777 для временных каталогов if os.Geteuid() == 0 {
chmodCmd := exec.Command("chmod", "777", path)
chmodCmd.Run() // Игнорируем ошибки
return nil
}
// Для обычной работы устанавливаем права и группу wheel
permissions := "2775"
group := "wheel"
var chmodCmd, chownCmd *exec.Cmd
if isRoot {
// Выполняем команды напрямую без sudo
chmodCmd = exec.Command("chmod", permissions, path)
chownCmd = exec.Command("chown", "root:"+group, path)
} else {
// Используем sudo для обычных пользователей
chmodCmd = exec.Command("sudo", "chmod", permissions, path)
chownCmd = exec.Command("sudo", "chown", "root:"+group, path)
}
// Устанавливаем права с setgid битом
err = chmodCmd.Run()
if err != nil {
// Для root игнорируем ошибки, если группа wheel не существует
if !isRoot {
return err
}
}
// Устанавливаем владельца root:wheel
err = chownCmd.Run()
if err != nil {
// Для root игнорируем ошибки, если группа wheel не существует
if !isRoot {
return err
}
}
return nil return nil
} }
// Для остальных каталогов обычное создание // В CI не проверяем привилегии
return os.MkdirAll(path, mode) if os.Getenv("CI") == "true" {
return nil
}
currentUser, err := user.Current()
if err != nil {
return fmt.Errorf("не удалось получить информацию о текущем пользователе: %w", err)
}
privilegedGroup := fsutils.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
} }

18
main.go
View File

@@ -50,6 +50,22 @@ func VersionCmd() *cli.Command {
} }
} }
func HelpCmd() *cli.Command {
return &cli.Command{
Name: "help",
Aliases: []string{"h"},
Usage: gotext.Get("Shows a list of commands or help for one command"),
ArgsUsage: "[command]",
Action: func(cCtx *cli.Context) error {
args := cCtx.Args()
if args.Present() {
return cli.ShowCommandHelp(cCtx, args.First())
}
return cli.ShowAppHelp(cCtx)
},
}
}
func GetApp() *cli.App { func GetApp() *cli.App {
return &cli.App{ return &cli.App{
Name: "alr", Name: "alr",
@@ -88,6 +104,7 @@ func GetApp() *cli.App {
InternalBuildCmd(), InternalBuildCmd(),
InternalInstallCmd(), InternalInstallCmd(),
InternalReposCmd(), InternalReposCmd(),
HelpCmd(),
}, },
Before: func(c *cli.Context) error { Before: func(c *cli.Context) error {
if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" { if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {
@@ -144,6 +161,7 @@ func main() {
// Make the application more internationalized // Make the application more internationalized
cli.AppHelpTemplate = cliutils.GetAppCliTemplate() cli.AppHelpTemplate = cliutils.GetAppCliTemplate()
cli.CommandHelpTemplate = cliutils.GetCommandHelpTemplate() cli.CommandHelpTemplate = cliutils.GetCommandHelpTemplate()
cli.SubcommandHelpTemplate = cliutils.GetSubcommandHelpTemplate()
cli.HelpFlag.(*cli.BoolFlag).Usage = gotext.Get("Show help") cli.HelpFlag.(*cli.BoolFlag).Usage = gotext.Get("Show help")
err = app.RunContext(ctx, os.Args) err = app.RunContext(ctx, os.Args)

View File

@@ -1,19 +1,3 @@
// 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

View File

@@ -27,9 +27,9 @@ import (
// createDir создает директорию с правильными правами для production // createDir создает директорию с правильными правами для production
func createDir(itemPath string, mode os.FileMode) error { func createDir(itemPath string, mode os.FileMode) error {
// Используем специальную функцию для создания каталогов с setgid битом только для /tmp/alr // Используем специальную функцию для создания каталогов с setgid битом только для /tmp/alr/ и /var/cache/alr/
// В остальных случаях используем обычное создание директории // Проверяем с слешем в конце, чтобы исключить тестовые директории вроде /tmp/alr-test-XXX
if strings.HasPrefix(itemPath, "/tmp/alr") { if strings.HasPrefix(itemPath, "/tmp/alr/") || strings.HasPrefix(itemPath, "/var/cache/alr/") {
return utils.EnsureTempDirWithRootOwner(itemPath, mode) return utils.EnsureTempDirWithRootOwner(itemPath, mode)
} else { } else {
return os.MkdirAll(itemPath, mode) return os.MkdirAll(itemPath, mode)

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

36
repo.go
View File

@@ -46,6 +46,24 @@ func RepoCmd() *cli.Command {
SetRepoRefCmd(), SetRepoRefCmd(),
RepoMirrorCmd(), RepoMirrorCmd(),
SetUrlCmd(), SetUrlCmd(),
RepoHelpCmd(),
},
}
}
func RepoHelpCmd() *cli.Command {
return &cli.Command{
Name: "help",
Aliases: []string{"h"},
Usage: gotext.Get("Shows a list of commands or help for one command"),
ArgsUsage: "[command]",
Action: func(cCtx *cli.Context) error {
args := cCtx.Args()
if args.Present() {
return cli.ShowCommandHelp(cCtx, args.First())
}
cli.ShowSubcommandHelp(cCtx)
return nil
}, },
} }
} }
@@ -331,6 +349,24 @@ func RepoMirrorCmd() *cli.Command {
AddMirror(), AddMirror(),
RemoveMirror(), RemoveMirror(),
ClearMirrors(), ClearMirrors(),
MirrorHelpCmd(),
},
}
}
func MirrorHelpCmd() *cli.Command {
return &cli.Command{
Name: "help",
Aliases: []string{"h"},
Usage: gotext.Get("Shows a list of commands or help for one command"),
ArgsUsage: "[command]",
Action: func(cCtx *cli.Context) error {
args := cCtx.Args()
if args.Present() {
return cli.ShowCommandHelp(cCtx, args.First())
}
cli.ShowSubcommandHelp(cCtx)
return nil
}, },
} }
} }

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

View File

@@ -26,7 +26,7 @@ import (
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.elara.ws/vercmp" "gitea.plemya-x.ru/xpamych/vercmp"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" "gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
@@ -114,7 +114,7 @@ func UpgradeCmd() *cli.Command {
} }
if len(updates) > 0 { if len(updates) > 0 {
err = builder.InstallALRPackages( _, err = builder.InstallPkgs(
ctx, ctx,
&build.BuildArgs{ &build.BuildArgs{
Opts: &types.BuildOpts{ Opts: &types.BuildOpts{
@@ -124,7 +124,7 @@ func UpgradeCmd() *cli.Command {
Info: deps.Info, Info: deps.Info,
PkgFormat_: build.GetPkgFormat(deps.Manager), PkgFormat_: build.GetPkgFormat(deps.Manager),
}, },
mapUptatesInfoToPackages(updates), mapUpdatesToPackageNames(updates),
) )
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err) return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err)
@@ -138,12 +138,19 @@ func UpgradeCmd() *cli.Command {
} }
} }
func mapUptatesInfoToPackages(updates []UpdateInfo) []alrsh.Package { func mapUpdatesToPackageNames(updates []UpdateInfo) []string {
var pkgs []alrsh.Package seen := make(map[string]bool)
var pkgNames []string
for _, info := range updates { for _, info := range updates {
pkgs = append(pkgs, *info.Package) fullName := fmt.Sprintf("%s+%s", info.Package.Name, info.Package.Repository)
if !seen[fullName] {
seen[fullName] = true
pkgNames = append(pkgNames, fullName)
}
} }
return pkgs
return pkgNames
} }
type UpdateInfo struct { type UpdateInfo struct {