39 Commits

Author SHA1 Message Date
bd79dcf401 feat: add firejailed support (#106)
Some checks failed
Update alr-git / changelog (push) Successful in 27s
Create Release / changelog (push) Failing after 3m15s
Reviewed-on: #106
Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
2025-06-15 13:16:02 +00:00
d1fe02fa57 feat: support single package repository
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 7m49s
Update alr-git / changelog (push) Successful in 31s
2025-06-14 22:26:33 +03:00
1ca7801fba fix(altlinux): use APT::Install::Virtual=true for install
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 8m40s
Update alr-git / changelog (push) Successful in 34s
2025-06-14 15:50:58 +03:00
661d79ce24 fix: remove debug logs and fix cache clearing 2025-06-14 15:05:18 +03:00
bece64c132 сi: update alr-spec
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 7m12s
Update alr-git / changelog (push) Successful in 28s
2025-06-12 22:42:15 +03:00
6d29b98cf7 ci: fix update-alr-git and disable e2e
Some checks failed
Pre-commit / pre-commit (pull_request) Successful in 6m26s
Update alr-git / changelog (push) Failing after 26s
2025-06-12 22:05:37 +03:00
d286041864 ci: add alr-git update
Some checks failed
E2E / tests (pull_request) Has been cancelled
Pre-commit / pre-commit (pull_request) Successful in 10m12s
Update alr-git / changelog (push) Failing after 58s
2025-06-12 16:57:35 +03:00
392a522723 refactor: keep only one struct for package 2025-06-12 16:25:18 +03:00
e259184a89 refactor: migrate to xorm 2025-06-10 14:12:40 +03:00
65ab4de561 refactor: move distro from internal to pkg 2025-06-09 22:58:34 +03:00
1cdab8dfed refactor: move alr.sh parsing to pkg 2025-06-09 17:56:46 +03:00
237e2c338d refactor: move types from internal to pkg 2025-06-09 13:34:43 +03:00
703ab8e8c4 refactor: move pkg/ to internal/ and update imports
Some checks failed
E2E / tests (pull_request) Has been cancelled
Pre-commit / pre-commit (pull_request) Successful in 5m35s
Restructure project by relocating package contents from pkg/ to internal/ to better reflect internal-only usage. This commit is initial step to prepare project for public api
2025-06-09 10:15:47 +03:00
06fcab4ce7 fix: prevent for building dependencies twice (#99)
closes #94

Reviewed-on: #99
Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
2025-06-08 17:57:18 +00:00
7741c7368b исправление ссылки на alr-LG 2025-06-06 13:49:14 +00:00
69f4af0a4d ci: fix
Some checks failed
Create Release / changelog (push) Failing after 2m2s
2025-05-30 20:01:16 +03:00
bcf627f176 ci: try add privileged true
Some checks failed
Create Release / changelog (push) Has been cancelled
2025-05-30 20:00:15 +03:00
6ec95e4bd9 ci: add bindfs install
Some checks failed
Create Release / changelog (push) Failing after 2m5s
2025-05-30 19:50:59 +03:00
578da7ff52 Revert "fix: use mount only for non-root users"
This reverts commit c51caf5c52.
2025-05-30 19:50:38 +03:00
c51caf5c52 fix: use mount only for non-root users
Some checks failed
Create Release / changelog (push) Failing after 2m0s
2025-05-30 19:41:17 +03:00
09dba577c6 ci: fix
Some checks failed
Create Release / changelog (push) Failing after 2m0s
2025-05-30 19:31:12 +03:00
ca82bf3024 ci: fix
Some checks failed
Create Release / changelog (push) Failing after 2m3s
2025-05-30 19:26:08 +03:00
c0023db6cd chore: fix install
Some checks failed
Create Release / changelog (push) Failing after 2m12s
2025-05-30 19:02:04 +03:00
152e5077ec ci(release): add make install
Some checks failed
Create Release / changelog (push) Failing after 2m0s
2025-05-30 18:53:17 +03:00
15ba8700e8 ci: set gitea/runner-images:ubuntu-latest for release
Some checks failed
Create Release / changelog (push) Failing after 1m50s
2025-05-30 17:36:51 +03:00
a8aefc0524 chore: replace git urls in tests
Some checks failed
E2E / tests (pull_request) Failing after 2m4s
Pre-commit / pre-commit (pull_request) Successful in 14m15s
Create Release / changelog (push) Has been cancelled
2025-05-30 07:49:58 +03:00
9540030579 изменение: скрипт установки берёт бинарники из релиза, README.md 2025-05-29 17:48:40 +03:00
4f9d4260b8 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 32s
2025-05-17 19:41:50 +03:00
38b5e6f581 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 33s
2025-05-17 19:19:09 +03:00
408bd12302 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 33s
2025-05-17 19:12:19 +03:00
fb83d544de добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 32s
2025-05-17 19:08:54 +03:00
2cb963d4b2 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 33s
2025-05-17 19:00:04 +03:00
e74d74cdf6 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 35s
2025-05-17 18:54:59 +03:00
5b3d53d253 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 34s
2025-05-17 18:41:57 +03:00
36e704f735 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза 2025-05-17 18:32:25 +03:00
07356d5e55 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 1m5s
2025-05-17 18:06:49 +03:00
52bd6aca93 Исправление README.md 2025-05-17 12:11:57 +03:00
2f1770b43b Дополнение README.md 2025-05-16 23:07:18 +03:00
9d5b5b51ff Дополнение README.md 2025-05-16 23:05:58 +03:00
93 changed files with 2556 additions and 1528 deletions

View File

@ -16,11 +16,12 @@
name: E2E
# on:
# push:
# branches: [ main ]
# pull_request:
on:
push:
branches: [ main ]
pull_request:
workflow_dispatch:
jobs:
tests:
@ -39,19 +40,18 @@ jobs:
uses: https://github.com/actions/setup-go@v5
with:
go-version: '1.24'
cache: false
- name: Cache Podman images
uses: actions/cache@v4
with:
path: |
~/.local/share/containers/storage
/var/lib/containers/storage
key: ${{ runner.os }}-primes
# - name: Cache Podman images
# uses: actions/cache@v4
# with:
# path: |
# ~/.local/share/containers/storage
# /var/lib/containers/storage
# key: ${{ runner.os }}-primes
- name: Run E2E tests
env:
DOCKER_HOST: unix:///tmp/podman.sock
IGNORE_ROOT_CHECK: 1
run: |
podman system service -t 0 unix:///tmp/podman.sock &
make e2e-test

View File

@ -26,6 +26,8 @@ on:
jobs:
pre-commit:
runs-on: ubuntu-latest
container:
image: docker.gitea.com/runner-images:ubuntu-latest
steps:
- name: Checkout

View File

@ -45,9 +45,15 @@ jobs:
echo "Version - $version"
echo "VERSION=$version" >> $GITHUB_ENV
- name: Build alr binary
- name: Prepare for install
run: |
CGO_ENABLED=0 go build -ldflags "-X gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=${{ env.VERSION }}" -o alr
apt-get update && apt-get install -y libcap2-bin bindfs
- name: Build alr
env:
IGNORE_ROOT_CHECK: 1
run: |
make build
- name: Create tar.gz
run: |
@ -64,4 +70,51 @@ jobs:
body: ${{ steps.changes.outputs.changes }}
files: |-
alr-${{ env.VERSION }}-linux-x86_64.tar.gz
- name: Checkout alr-default repository
uses: actions/checkout@v4
with:
repository: Plemya-x/alr-default
token: ${{ secrets.GITEAPUBLIC }}
path: alr-default
- name: Update version 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/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh
- name: Install alr
run: |
make install
# temporary fix
groupadd wheel
usermod -aG wheel root
- name: Build packages
run: |
SCRIPT_PATH=alr-default/alr-bin/alr.sh
ALR_DISTRO=altlinux ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
ALR_PKG_FORMAT=deb alr build -s "$SCRIPT_PATH"
ALR_PKG_FORMAT=archlinux alr build -s "$SCRIPT_PATH"
- name: Upload assets
uses: akkuman/gitea-release-action@v1
with:
body: ${{ steps.changes.outputs.changes }}
files: |-
alr-bin+alr-default_${{ env.VERSION }}-1.red80_amd64.deb \
alr-bin+alr-default-${{ env.VERSION }}-1-x86_64.pkg.tar.zst \
alr-bin+alr-default-${{ env.VERSION }}-1.red80.x86_64.rpm \
alr-bin+alr-default-${{ env.VERSION }}-alt1.x86_64.rpm
- name: Commit changes
run: |
cd alr-default
git config user.name "gitea"
git config user.email "admin@plemya-x.ru"
git add .
git commit -m "Обновление версии до ${{ env.VERSION }}"
git push

View File

@ -0,0 +1,69 @@
# 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/>.
name: Update alr-git
on:
push:
branches:
- master
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v6
- name: Setup alr-spec
run: |
uv tool install alr-spec==0.0.5
- name: Install alr
run: |
apt-get update && apt-get install -y libcap2-bin
curl -fsS https://gitea.plemya-x.ru/Plemya-x/ALR/raw/branch/master/scripts/install.sh | bash
- name: Checkout this repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set ALR version
run: |
echo "NEW_ALR_VERSION=$(alr helper git-version)" >> $GITHUB_ENV
- name: Checkout alr-default repository
uses: actions/checkout@v4
with:
repository: Plemya-x/alr-default
token: ${{ secrets.GITEAPUBLIC }}
path: alr-default
- name: Update version
working-directory: ./alr-default/alr-git
run: |
alr-spec set-field version $NEW_ALR_VERSION
alr-spec set-field release 1
- name: Commit changes
run: |
cd alr-default
git config user.name "gitea"
git config user.email "admin@plemya-x.ru"
git add .
git commit -m "Обновление версии до $NEW_ALR_VERSION"
git push

View File

@ -24,7 +24,7 @@ $(BIN):
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
check-no-root:
@if [[ "$(IGNORE_ROOT_CHECK)" != "1" ]] && [[ "$$(whoami)" == 'root' ]]; then \
@if [ "$$IGNORE_ROOT_CHECK" != "1" ] && [ "`whoami`" = "root" ]; then \
echo "This target shouldn't run as root" 1>&2; \
echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \
exit 1; \
@ -39,6 +39,12 @@ install: \
$(INSTALED_BIN): $(BIN)
install -Dm755 $< $@
setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN)
@if id alr >/dev/null 2>&1; then \
echo "User 'alr' already exists. Skipping."; \
else \
useradd -r -s /usr/sbin/nologin alr; \
fi
install -d -o alr -g alr -m 755 /var/cache/alr /etc/alr
$(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION)
install -Dm755 $< $@

View File

@ -20,10 +20,10 @@ ALR написан на чистом Go и после сборки не имее
Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду:
```bash
curl -fsSL plemya-x.ru/alr/install.sh | bash
curl -fsSL https://gitea.plemya-x.ru/Plemya-x/ALR/raw/branch/master/scripts/install.sh | bash
```
**ВАЖНО**: При этом скрипт будет загружен и запущен с <https://plemya-x.ru/alr/install.sh>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их.
**ВАЖНО**: При этом скрипт будет загружен и запущен [скрипт](https://gitea.plemya-x.ru/Plemya-x/ALR/src/branch/master/scripts/install.sh). Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их.
### Сборка из исходного кода
@ -52,9 +52,17 @@ ALR был создан потому, что упаковка программн
Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.
Например, репозиторий [Plemya-x/alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так:
Например, репозиторий с ALR [Plemya-x/alr-default](https://gitea.plemya-x.ru/Plemya-x/alr-default.git)
```
alr addrepo --name alr-repo --url https://gitea.plemya-x.ru/Plemya-x/alr-repo.git
alr repo add alr-default https://gitea.plemya-x.ru/Plemya-x/alr-default.git
```
Репозиторий пакетов [Plemya-x/alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так:
```
alr repo add alr-repo https://gitea.plemya-x.ru/Plemya-x/alr-repo.git
```
Репозиторий Linux-Gaming [Plemya-x/alr-LG](https://gitea.plemya-x.ru/Plemya-x/alr-LG.git) можно подключить так:
```
alr repo add alr-LG https://gitea.plemya-x.ru/Plemya-x/alr-LG.git
```
---

View File

@ -11,7 +11,7 @@
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">16.9%</text>
<text x="86" y="14">16.9%</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">19.0%</text>
<text x="86" y="14">19.0%</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 926 B

View File

@ -28,12 +28,12 @@ import (
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
func BuildCmd() *cli.Command {
@ -97,7 +97,7 @@ func BuildCmd() *cli.Command {
var script string
var packages []string
var res *build.BuildResult
var res []*build.BuiltDep
var scriptArgs *build.BuildPackageFromScriptArgs
var dbArgs *build.BuildPackageFromDbArgs
@ -222,9 +222,9 @@ func BuildCmd() *cli.Command {
return cliutils.FormatCliExit(gotext.Get("Error building package"), err)
}
for _, pkgPath := range res.PackagePaths {
name := filepath.Base(pkgPath)
err = osutils.Move(pkgPath, filepath.Join(wd, name))
for _, pkg := range res {
name := filepath.Base(pkg.Path)
err = osutils.Move(pkg.Path, filepath.Join(wd, name))
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err)
}

View File

@ -182,7 +182,7 @@ func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expect
}
const REPO_NAME_FOR_E2E_TESTS = "alr-repo"
const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git"
const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git"
func defaultPrepare(t *testing.T, r e2e.Runnable) {
execShouldNoError(t, r,

View File

@ -0,0 +1,41 @@
// 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/>.
//go:build e2e
package e2etests_test
import (
"fmt"
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EFirejailedPackage(t *testing.T) {
dockerMultipleRun(
t,
"firejailed-package",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg", REPO_NAME_FOR_E2E_TESTS))
execShouldError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg-incorrect", REPO_NAME_FOR_E2E_TESTS))
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh'")
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh.profile'")
},
)
}

View File

@ -31,8 +31,8 @@ func TestE2EGroupAndSummaryField(t *testing.T) {
RPM_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group}}\" | grep ^System/Base$")
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary}}\" | grep \"^Custom summary$\"")
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group.Resolved}}\" | grep ^System/Base$")
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary.Resolved}}\" | grep \"^Custom summary$\"")
},
)
}

View File

@ -0,0 +1,53 @@
// 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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func Test75SinglePackageRepo(t *testing.T) {
dockerMultipleRun(
t,
"issue-76-single-package-repo",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
execShouldNoError(t, r,
"sudo",
"alr",
"repo",
"add",
REPO_NAME_FOR_E2E_TESTS,
"https://gitea.plemya-x.ru/Maks1mS/test-single-package-alr-repo.git",
)
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "1075c918be")
execShouldNoError(t, r, "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "in", "test-single-repo")
execShouldNoError(t, r, "sh", "-c", "alr list -U")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "5e361c50d7")
execShouldNoError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1")
execShouldNoError(t, r, "sudo", "alr", "up")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
},
)
}

View File

@ -0,0 +1,49 @@
// 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/>.
//go:build e2e
package e2etests_test
import (
"bytes"
"strings"
"testing"
"github.com/efficientgo/e2e"
"github.com/stretchr/testify/assert"
)
func TestE2EIssue94TwiceBuild(t *testing.T) {
dockerMultipleRun(
t,
"issue-94-twice-build",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
var stderr bytes.Buffer
err := r.Exec(
e2e.NewCommand("sudo", "alr", "in", "test-94-app"),
e2e.WithExecOptionStderr(&stderr),
)
assert.NoError(t, err, "command failed")
output := stderr.String()
assert.Equal(t, 1, strings.Count(output, "Building package name=test-94-dep"))
},
)
}

30
fix.go
View File

@ -20,6 +20,7 @@
package main
import (
"io/fs"
"log/slog"
"os"
"path/filepath"
@ -57,7 +58,6 @@ func FixCmd() *cli.Command {
paths := cfg.GetPaths()
slog.Info(gotext.Get("Clearing cache directory"))
// Remove all nested directories of paths.CacheDir
dir, err := os.Open(paths.CacheDir)
if err != nil {
@ -71,7 +71,13 @@ func FixCmd() *cli.Command {
}
for _, entry := range entries {
err = os.RemoveAll(filepath.Join(paths.CacheDir, entry))
fullPath := filepath.Join(paths.CacheDir, entry)
if err := makeWritableRecursive(fullPath); err != nil {
slog.Debug("Failed to make path writable", "path", fullPath, "error", err)
}
err = os.RemoveAll(fullPath)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err)
}
@ -101,3 +107,23 @@ func FixCmd() *cli.Command {
},
}
}
func makeWritableRecursive(path string) error {
return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
info, err := d.Info()
if err != nil {
return err
}
newMode := info.Mode() | 0o200
if d.IsDir() {
newMode |= 0o100
}
return os.Chmod(path, newMode)
})
}

2
gen.go
View File

@ -25,7 +25,7 @@ import (
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/gen"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/gen"
)
func GenCmd() *cli.Command {

12
go.mod
View File

@ -8,7 +8,6 @@ require (
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PuerkitoBio/purell v1.2.0
github.com/alecthomas/assert/v2 v2.2.1
github.com/alecthomas/chroma/v2 v2.9.1
github.com/caarlos0/env v3.5.0+incompatible
github.com/charmbracelet/bubbles v0.20.0
@ -42,6 +41,7 @@ require (
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.25.0
mvdan.cc/sh/v3 v3.10.0
xorm.io/xorm v1.3.9
)
require (
@ -52,7 +52,6 @@ require (
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // indirect
github.com/alecthomas/repr v0.2.0 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
@ -79,6 +78,7 @@ require (
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.8.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
@ -90,10 +90,10 @@ require (
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
@ -105,6 +105,8 @@ require (
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect
@ -117,9 +119,10 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
@ -145,4 +148,5 @@ require (
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect
)

43
go.sum
View File

@ -17,6 +17,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
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/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
@ -137,6 +139,7 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@ -153,10 +156,13 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -176,6 +182,7 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -187,6 +194,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@ -229,6 +237,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -245,6 +255,8 @@ github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@ -269,8 +281,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8=
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
@ -306,6 +319,11 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@ -320,6 +338,10 @@ github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
@ -358,8 +380,9 @@ github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtC
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
@ -374,6 +397,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -382,6 +406,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns=
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
@ -453,6 +479,7 @@ golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -488,6 +515,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -612,8 +640,13 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
@ -659,3 +692,7 @@ mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=

15
info.go
View File

@ -30,9 +30,9 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
@ -67,15 +67,8 @@ func InfoCmd() *cli.Command {
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
}
defer result.Close()
for result.Next() {
var pkg database.Package
err = result.StructScan(&pkg)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err)
}
for _, pkg := range result {
fmt.Println(pkg.Name)
}
return nil
@ -96,6 +89,7 @@ func InfoCmd() *cli.Command {
New(ctx).
WithConfig().
WithDB().
WithDistroInfo().
WithRepos().
Build()
if err != nil {
@ -144,7 +138,8 @@ func InfoCmd() *cli.Command {
for _, pkg := range pkgs {
if !all {
err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
alrsh.ResolvePackage(&pkg, names)
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
}

View File

@ -25,13 +25,12 @@ import (
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
func InstallCmd() *cli.Command {
@ -98,7 +97,7 @@ func InstallCmd() *cli.Command {
return err
}
err = builder.InstallPkgs(
_, err = builder.InstallPkgs(
ctx,
&build.BuildArgs{
Opts: &types.BuildOpts{
@ -136,15 +135,8 @@ func InstallCmd() *cli.Command {
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
}
defer result.Close()
for result.Next() {
var pkg database.Package
err = result.StructScan(&pkg)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err)
}
for _, pkg := range result {
fmt.Println(pkg.Name)
}
@ -190,20 +182,12 @@ func RemoveCmd() *cli.Command {
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
}
defer result.Close()
for result.Next() {
var pkg database.Package
err = result.StructScan(&pkg)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err)
}
for _, pkg := range result {
_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
if !ok {
continue
}
fmt.Println(pkg.Name)
}

View File

@ -32,14 +32,14 @@ import (
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"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/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
func InternalBuildCmd() *cli.Command {

View File

@ -24,18 +24,17 @@ import (
"context"
"encoding/gob"
"errors"
"fmt"
"log/slog"
"github.com/leonelquinteros/gotext"
"mvdan.cc/sh/v3/syntax"
"mvdan.cc/sh/v3/syntax/typedjson"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"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/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type BuildInput struct {
@ -133,54 +132,33 @@ type RepositoryProvider interface {
// ================================================
type ScriptFile struct {
File *syntax.File
type BuiltDep struct {
Name string
Path string
}
func (s *ScriptFile) GobEncode() ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(s.Path); err != nil {
return nil, err
func Map[T, R any](items []T, f func(T) R) []R {
res := make([]R, len(items))
for i, item := range items {
res[i] = f(item)
}
var fileBuf bytes.Buffer
if err := typedjson.Encode(&fileBuf, s.File); err != nil {
return nil, err
}
fileData := fileBuf.Bytes()
if err := enc.Encode(fileData); err != nil {
return nil, err
}
return buf.Bytes(), nil
return res
}
func (s *ScriptFile) GobDecode(data []byte) error {
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
if err := dec.Decode(&s.Path); err != nil {
return err
}
var fileData []byte
if err := dec.Decode(&fileData); err != nil {
return err
}
fileReader := bytes.NewReader(fileData)
file, err := typedjson.Decode(fileReader)
if err != nil {
return err
}
s.File = file.(*syntax.File)
return nil
func GetBuiltPaths(deps []*BuiltDep) []string {
return Map(deps, func(dep *BuiltDep) string {
return dep.Path
})
}
type BuildResult struct {
PackagePaths []string
PackageNames []string
func GetBuiltName(deps []*BuiltDep) []string {
return Map(deps, func(dep *BuiltDep) string {
return dep.Name
})
}
type PackageFinder interface {
FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error)
FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error)
}
type Config interface {
@ -195,12 +173,12 @@ type FunctionsOutput struct {
// EXECUTORS
type ScriptResolverExecutor interface {
ResolveScript(ctx context.Context, pkg *db.Package) *ScriptInfo
ResolveScript(ctx context.Context, pkg *alrsh.Package) *ScriptInfo
}
type ScriptExecutor interface {
ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error)
ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error)
ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error)
ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error)
PrepareDirs(
ctx context.Context,
input *BuildInput,
@ -209,27 +187,27 @@ type ScriptExecutor interface {
ExecuteSecondPass(
ctx context.Context,
input *BuildInput,
sf *ScriptFile,
varsOfPackages []*types.BuildVars,
sf *alrsh.ScriptFile,
varsOfPackages []*alrsh.Package,
repoDeps []string,
builtNames []string,
builtDeps []*BuiltDep,
basePkg string,
) (*SecondPassResult, error)
) ([]*BuiltDep, error)
}
type CacheExecutor interface {
CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *types.BuildVars) (string, bool, error)
CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *alrsh.Package) (string, bool, error)
}
type ScriptViewerExecutor interface {
ViewScript(ctx context.Context, input *BuildInput, sf *ScriptFile, basePkg string) error
ViewScript(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile, basePkg string) error
}
type CheckerExecutor interface {
PerformChecks(
ctx context.Context,
input *BuildInput,
vars *types.BuildVars,
vars *alrsh.Package,
) (bool, error)
}
@ -307,7 +285,7 @@ func (b *BuildArgs) PkgFormat() string {
type BuildPackageFromDbArgs struct {
BuildArgs
Package *db.Package
Package *alrsh.Package
Packages []string
}
@ -320,7 +298,7 @@ type BuildPackageFromScriptArgs struct {
func (b *Builder) BuildPackageFromDb(
ctx context.Context,
args *BuildPackageFromDbArgs,
) (*BuildResult, error) {
) ([]*BuiltDep, error) {
scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package)
return b.BuildPackage(ctx, &BuildInput{
@ -336,7 +314,7 @@ func (b *Builder) BuildPackageFromDb(
func (b *Builder) BuildPackageFromScript(
ctx context.Context,
args *BuildPackageFromScriptArgs,
) (*BuildResult, error) {
) ([]*BuiltDep, error) {
return b.BuildPackage(ctx, &BuildInput{
script: args.Script,
repository: "default",
@ -350,43 +328,46 @@ func (b *Builder) BuildPackageFromScript(
func (b *Builder) BuildPackage(
ctx context.Context,
input *BuildInput,
) (*BuildResult, error) {
) ([]*BuiltDep, error) {
scriptPath := input.script
slog.Debug("ReadScript")
sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed reading script: %w", err)
}
slog.Debug("ExecuteFirstPass")
basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed ExecuteFirstPass: %w", err)
}
builtPaths := make([]string, 0)
var builtDeps []*BuiltDep
if !input.opts.Clean {
var remainingVars []*types.BuildVars
var remainingVars []*alrsh.Package
for _, vars := range varsOfPackages {
builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars)
if err != nil {
return nil, err
}
if ok {
builtPaths = append(builtPaths, builtPkgPath)
builtDeps = append(builtDeps, &BuiltDep{
Path: builtPkgPath,
})
} else {
remainingVars = append(remainingVars, vars)
}
}
if len(remainingVars) == 0 {
return &BuildResult{builtPaths, nil}, nil
return builtDeps, nil
}
}
slog.Debug("ViewScript")
slog.Debug("", "varsOfPackages", varsOfPackages)
err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg)
if err != nil {
return nil, err
@ -410,11 +391,11 @@ func (b *Builder) BuildPackage(
sources := []string{}
checksums := []string{}
for _, vars := range varsOfPackages {
buildDepends = append(buildDepends, vars.BuildDepends...)
optDepends = append(optDepends, vars.OptDepends...)
depends = append(depends, vars.Depends...)
sources = append(sources, vars.Sources...)
checksums = append(checksums, vars.Checksums...)
buildDepends = append(buildDepends, vars.BuildDepends.Resolved()...)
optDepends = append(optDepends, vars.OptDepends.Resolved()...)
depends = append(depends, vars.Depends.Resolved()...)
sources = append(sources, vars.Sources.Resolved()...)
checksums = append(checksums, vars.Checksums.Resolved()...)
}
buildDepends = removeDuplicates(buildDepends)
optDepends = removeDuplicates(optDepends)
@ -427,19 +408,32 @@ func (b *Builder) BuildPackage(
sources, checksums = removeDuplicatesSources(sources, checksums)
slog.Debug("installBuildDeps")
err = b.installBuildDeps(ctx, input, buildDepends)
alrBuildDeps, err := b.installBuildDeps(ctx, input, buildDepends)
if err != nil {
return nil, err
}
slog.Debug("installOptDeps")
err = b.installOptDeps(ctx, input, optDepends)
_, err = b.installOptDeps(ctx, input, optDepends)
if err != nil {
return nil, err
}
depNames := make(map[string]struct{})
for _, dep := range alrBuildDeps {
depNames[dep.Name] = struct{}{}
}
// We filter so as not to re-build what has already been built at the `installBuildDeps` stage.
var filteredDepends []string
for _, d := range depends {
if _, found := depNames[d]; !found {
filteredDepends = append(filteredDepends, d)
}
}
slog.Debug("BuildALRDeps")
builtPaths, builtNames, repoDeps, err := b.BuildALRDeps(ctx, input, depends)
newBuiltDeps, repoDeps, err := b.BuildALRDeps(ctx, input, filteredDepends)
if err != nil {
return nil, err
}
@ -450,8 +444,6 @@ func (b *Builder) BuildPackage(
return nil, err
}
// builtPaths = append(builtPaths, newBuildPaths...)
slog.Info(gotext.Get("Downloading sources"))
slog.Debug("DownloadSources")
err = b.sourceExecutor.DownloadSources(
@ -467,6 +459,8 @@ func (b *Builder) BuildPackage(
return nil, err
}
builtDeps = removeDuplicates(append(builtDeps, newBuiltDeps...))
slog.Debug("ExecuteSecondPass")
res, err := b.scriptExecutor.ExecuteSecondPass(
ctx,
@ -474,25 +468,21 @@ func (b *Builder) BuildPackage(
sf,
varsOfPackages,
repoDeps,
builtNames,
builtDeps,
basePkg,
)
if err != nil {
return nil, err
}
pkgPaths := removeDuplicates(append(builtPaths, res.BuiltPaths...))
pkgNames := removeDuplicates(append(builtNames, res.BuiltNames...))
builtDeps = removeDuplicates(append(builtDeps, res...))
return &BuildResult{
PackagePaths: pkgPaths,
PackageNames: pkgNames,
}, nil
return builtDeps, nil
}
type InstallPkgsArgs struct {
BuildArgs
AlrPkgs []db.Package
AlrPkgs []alrsh.Package
NativePkgs []string
}
@ -503,7 +493,7 @@ func (b *Builder) InstallALRPackages(
BuildOptsProvider
PkgFormatProvider
},
alrPkgs []db.Package,
alrPkgs []alrsh.Package,
) error {
for _, pkg := range alrPkgs {
res, err := b.BuildPackageFromDb(
@ -523,7 +513,7 @@ func (b *Builder) InstallALRPackages(
}
err = b.installerExecutor.InstallLocal(
res.PackagePaths,
GetBuiltPaths(res),
&manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
},
@ -544,13 +534,13 @@ func (b *Builder) BuildALRDeps(
PkgFormatProvider
},
depends []string,
) (builtPaths, builtNames, repoDeps []string, err error) {
) (buildDeps []*BuiltDep, repoDeps []string, err error) {
if len(depends) > 0 {
slog.Info(gotext.Get("Installing dependencies"))
found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей
if err != nil {
return nil, nil, nil, err
return nil, nil, fmt.Errorf("failed FindPkgs: %w", err)
}
repoDeps = notFound
@ -562,7 +552,7 @@ func (b *Builder) BuildALRDeps(
input.BuildOpts().Interactive,
)
type item struct {
pkg *db.Package
pkg *alrsh.Package
packages []string
}
pkgsMap := make(map[string]*item)
@ -597,20 +587,17 @@ func (b *Builder) BuildALRDeps(
},
)
if err != nil {
return nil, nil, nil, err
return nil, nil, fmt.Errorf("failed build package from db: %w", err)
}
builtPaths = append(builtPaths, res.PackagePaths...)
builtNames = append(builtNames, res.PackageNames...)
buildDeps = append(buildDeps, res...)
}
}
// Удаляем возможные дубликаты, которые могут быть введены, если
// несколько зависимостей зависят от одних и тех же пакетов.
repoDeps = removeDuplicates(repoDeps)
builtPaths = removeDuplicates(builtPaths)
builtNames = removeDuplicates(builtNames)
return builtPaths, builtNames, repoDeps, nil
buildDeps = removeDuplicates(buildDeps)
return buildDeps, repoDeps, nil
}
func (i *Builder) installBuildDeps(
@ -621,19 +608,20 @@ func (i *Builder) installBuildDeps(
PkgFormatProvider
},
pkgs []string,
) error {
) ([]*BuiltDep, error) {
var builtDeps []*BuiltDep
if len(pkgs) > 0 {
deps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
if err != nil {
return err
return nil, err
}
err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты
builtDeps, err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты
if err != nil {
return err
return nil, err
}
}
return nil
return builtDeps, nil
}
func (i *Builder) installOptDeps(
@ -644,10 +632,11 @@ func (i *Builder) installOptDeps(
PkgFormatProvider
},
pkgs []string,
) error {
) ([]*BuiltDep, error) {
var builtDeps []*BuiltDep
optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
if err != nil {
return err
return nil, err
}
if len(optDeps) > 0 {
optDeps, err := cliutils.ChooseOptDepends(
@ -657,19 +646,19 @@ func (i *Builder) installOptDeps(
input.BuildOpts().Interactive,
) // Пользователя просят выбрать опциональные зависимости
if err != nil {
return err
return nil, err
}
if len(optDeps) == 0 {
return nil
return builtDeps, nil
}
err = i.InstallPkgs(ctx, input, optDeps) // Устанавливаем выбранные пакеты
builtDeps, err = i.InstallPkgs(ctx, input, optDeps) // Устанавливаем выбранные пакеты
if err != nil {
return err
return nil, err
}
}
return nil
return builtDeps, nil
}
func (i *Builder) InstallPkgs(
@ -680,18 +669,18 @@ func (i *Builder) InstallPkgs(
PkgFormatProvider
},
pkgs []string,
) error {
builtPaths, _, repoDeps, err := i.BuildALRDeps(ctx, input, pkgs)
) ([]*BuiltDep, error) {
builtDeps, repoDeps, err := i.BuildALRDeps(ctx, input, pkgs)
if err != nil {
return err
return nil, err
}
if len(builtPaths) > 0 {
err = i.installerExecutor.InstallLocal(builtPaths, &manager.Opts{
if len(builtDeps) > 0 {
err = i.installerExecutor.InstallLocal(GetBuiltPaths(builtDeps), &manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
})
if err != nil {
return err
return nil, err
}
}
@ -700,9 +689,9 @@ func (i *Builder) InstallPkgs(
NoConfirm: !input.BuildOpts().Interactive,
})
if err != nil {
return err
return nil, err
}
}
return nil
return builtDeps, nil
}

View File

@ -28,9 +28,9 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
type TestPackageFinder struct {

View File

@ -23,7 +23,7 @@ import (
"github.com/goreleaser/nfpm/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type Cache struct {
@ -33,7 +33,7 @@ type Cache struct {
func (c *Cache) CheckForBuiltPackage(
ctx context.Context,
input *BuildInput,
vars *types.BuildVars,
vars *alrsh.Package,
) (string, bool, error) {
filename, err := pkgFileName(input, vars)
if err != nil {
@ -56,7 +56,7 @@ func pkgFileName(
PkgFormatProvider
RepositoryProvider
},
vars *types.BuildVars,
vars *alrsh.Package,
) (string, error) {
pkgInfo := getBasePkgInfo(vars, input)

View File

@ -24,8 +24,8 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type Checker struct {
@ -35,7 +35,7 @@ type Checker struct {
func (c *Checker) PerformChecks(
ctx context.Context,
input *BuildInput,
vars *types.BuildVars,
vars *alrsh.Package,
) (bool, error) {
if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры
cont, err := cliutils.YesNoPrompt(

View File

@ -19,7 +19,7 @@ package build
import (
"path/filepath"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type BaseDirProvider interface {

View File

@ -27,7 +27,7 @@ import (
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, envs []string, updateFunc func(string)) error {

View File

@ -23,7 +23,7 @@ import (
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type EmptyFindProvReq struct{}

View File

@ -28,7 +28,7 @@ import (
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type FedoraFindProvReq struct{}

View File

@ -21,8 +21,8 @@ import (
"github.com/goreleaser/nfpm/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type ProvReqFinder interface {

237
internal/build/firejail.go Normal file
View File

@ -0,0 +1,237 @@
// 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 (
"errors"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"strings"
"github.com/goreleaser/nfpm/v2/files"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
const (
firejailedDir = "/usr/lib/alr/firejailed"
defaultDirMode = 0o755
defaultScriptMode = 0o755
)
var (
ErrInvalidDestination = errors.New("invalid destination path")
ErrMissingProfile = errors.New("default profile is missing")
ErrEmptyPackageName = errors.New("package name cannot be empty")
)
var binaryDirectories = []string{
"/usr/bin/",
"/bin/",
"/usr/local/bin/",
}
func applyFirejailIntegration(
vars *alrsh.Package,
dirs types.Directories,
contents []*files.Content,
) ([]*files.Content, error) {
slog.Info(gotext.Get("Applying FireJail integration"), "package", vars.Name)
if err := createFirejailedDirectory(dirs.PkgDir); err != nil {
return nil, fmt.Errorf("failed to create firejailed directory: %w", err)
}
newContents, err := processBinaryFiles(vars, contents, dirs)
if err != nil {
return nil, fmt.Errorf("failed to process binary files: %w", err)
}
return append(contents, newContents...), nil
}
func createFirejailedDirectory(pkgDir string) error {
firejailedPath := filepath.Join(pkgDir, firejailedDir)
return os.MkdirAll(firejailedPath, defaultDirMode)
}
func processBinaryFiles(pkg *alrsh.Package, contents []*files.Content, dirs types.Directories) ([]*files.Content, error) {
var newContents []*files.Content
for _, content := range contents {
if content.Type == "dir" {
continue
}
if !isBinaryFile(content.Destination) {
slog.Debug("content not binary file", "content", content)
continue
}
slog.Debug("process content", "content", content)
newContent, err := createFirejailedBinary(pkg, content, dirs)
if err != nil {
return nil, fmt.Errorf("failed to create firejailed binary for %s: %w", content.Destination, err)
}
if newContent != nil {
newContents = append(newContents, newContent...)
}
}
return newContents, nil
}
func isBinaryFile(destination string) bool {
for _, binDir := range binaryDirectories {
if strings.HasPrefix(destination, binDir) {
return true
}
}
return false
}
func createFirejailedBinary(
pkg *alrsh.Package,
content *files.Content,
dirs types.Directories,
) ([]*files.Content, error) {
origFilePath, err := generateFirejailedPath(content.Destination)
if err != nil {
return nil, err
}
profiles := pkg.FireJailProfiles.Resolved()
sourceProfilePath, ok := profiles[content.Destination]
if !ok {
sourceProfilePath, ok = profiles["default"]
if !ok {
return nil, errors.New("default profile is missing")
}
}
sourceProfilePath = filepath.Join(dirs.ScriptDir, sourceProfilePath)
dest, err := createFirejailProfilePath(content.Destination)
if err != nil {
return nil, err
}
err = createProfile(filepath.Join(dirs.PkgDir, dest), sourceProfilePath)
if err != nil {
return nil, err
}
if err := osutils.Move(content.Source, filepath.Join(dirs.PkgDir, origFilePath)); err != nil {
return nil, fmt.Errorf("failed to move original binary: %w", err)
}
// Create wrapper script
if err := createWrapperScript(content.Source, origFilePath, dest); err != nil {
return nil, fmt.Errorf("failed to create wrapper script: %w", err)
}
profile, err := getContentFromPath(dest, dirs.PkgDir)
if err != nil {
return nil, err
}
bin, err := getContentFromPath(origFilePath, dirs.PkgDir)
if err != nil {
return nil, err
}
return []*files.Content{
bin,
profile,
}, nil
}
func getContentFromPath(path, base string) (*files.Content, error) {
absPath := filepath.Join(base, path)
fi, err := os.Lstat(absPath)
if err != nil {
return nil, fmt.Errorf("failed to get file info: %w", err)
}
return &files.Content{
Source: absPath,
Destination: path,
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
Size: fi.Size(),
},
}, nil
}
func generateSafeName(destination string) (string, error) {
cleanPath := strings.TrimPrefix(destination, ".")
if cleanPath == "" {
return "", fmt.Errorf("invalid destination path: %s", destination)
}
return strings.ReplaceAll(cleanPath, "/", "_"), nil
}
func generateFirejailedPath(destination string) (string, error) {
safeName, err := generateSafeName(destination)
if err != nil {
return "", err
}
return filepath.Join(firejailedDir, safeName), nil
}
func createProfile(destProfilePath, profilePath string) error {
srcFile, err := os.Open(profilePath)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := os.Create(destProfilePath)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
if err != nil {
return err
}
return destFile.Sync()
}
func createWrapperScript(scriptPath, origFilePath, profilePath string) error {
scriptContent := fmt.Sprintf("#!/bin/bash\nexec firejail --profile=%q %q \"$@\"\n", profilePath, origFilePath)
return os.WriteFile(scriptPath, []byte(scriptContent), defaultDirMode)
}
func createFirejailProfilePath(binaryPath string) (string, error) {
name, err := generateSafeName(binaryPath)
if err != nil {
return "", err
}
return filepath.Join(firejailedDir, fmt.Sprintf("%s.profile", name)), nil
}

View File

@ -0,0 +1,316 @@
// 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 (
"os"
"path/filepath"
"testing"
"github.com/goreleaser/nfpm/v2/files"
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
func TestIsBinaryFile(t *testing.T) {
tests := []struct {
name string
destination string
expected bool
}{
{"usr/bin binary", "/usr/bin/test", true},
{"bin binary", "/bin/test", true},
{"usr/local/bin binary", "/usr/local/bin/test", true},
{"lib file", "/usr/lib/test.so", false},
{"etc file", "/etc/config", false},
{"empty destination", "", false},
{"root level file", "./test", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isBinaryFile(tt.destination)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGenerateSafeName(t *testing.T) {
tests := []struct {
name string
destination string
expected string
expectError bool
}{
{"usr/bin path", "./usr/bin/test", "_usr_bin_test", false},
{"bin path", "./bin/test", "_bin_test", false},
{"nested path", "./usr/local/bin/app", "_usr_local_bin_app", false},
{"path with spaces", "./usr/bin/my app", "_usr_bin_my app", false},
{"empty after trim", ".", "", true},
{"empty string", "", "", true},
{"only dots", "..", ".", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := generateSafeName(tt.destination)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, result)
}
})
}
}
func TestCreateWrapperScript(t *testing.T) {
tests := []struct {
name string
origFilePath string
profilePath string
expectedContent string
}{
{
"basic wrapper",
"/usr/lib/alr/firejailed/_usr_bin_test",
"/usr/lib/alr/firejailed/_usr_bin_test.profile",
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_test.profile\" \"/usr/lib/alr/firejailed/_usr_bin_test\" \"$@\"\n",
},
{
"path with spaces",
"/usr/lib/alr/firejailed/_usr_bin_my_app",
"/usr/lib/alr/firejailed/_usr_bin_my_app.profile",
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_my_app.profile\" \"/usr/lib/alr/firejailed/_usr_bin_my_app\" \"$@\"\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, "wrapper.sh")
err := createWrapperScript(scriptPath, tt.origFilePath, tt.profilePath)
assert.NoError(t, err)
assert.FileExists(t, scriptPath)
content, err := os.ReadFile(scriptPath)
assert.NoError(t, err)
assert.Equal(t, tt.expectedContent, string(content))
// Check file permissions
info, err := os.Stat(scriptPath)
assert.NoError(t, err)
assert.Equal(t, os.FileMode(defaultDirMode), info.Mode())
})
}
}
func TestCreateFirejailedBinary(t *testing.T) {
tests := []struct {
name string
setupFunc func(string) (*alrsh.Package, *files.Content, types.Directories)
expectError bool
errorMsg string
}{
{
"successful creation with default profile",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
defaultProfile := filepath.Join(scriptDir, "default.profile")
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile\nnet none"), 0o644)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
"": {"default": "default.profile"},
}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{
Source: srcBinary,
Destination: "./usr/bin/test-binary",
Type: "file",
}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
false,
"",
},
{
"successful creation with specific profile",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "special-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755)
defaultProfile := filepath.Join(scriptDir, "default.profile")
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
specialProfile := filepath.Join(scriptDir, "special.profile")
os.WriteFile(specialProfile, []byte("include /etc/firejail/default.profile\nnet none\nprivate-tmp"), 0o644)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
"": {"default": "default.profile", "/usr/bin/special-binary": "special.profile"},
}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{
Source: srcBinary,
Destination: "./usr/bin/special-binary",
Type: "file",
}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
false,
"",
},
{
"missing default profile",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {}}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
true,
"default profile is missing",
},
{
"profile file not found",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "nonexistent.profile"}}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
true,
"",
},
{
"invalid destination path",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
defaultProfile := filepath.Join(scriptDir, "default.profile")
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
pkg := &alrsh.Package{Name: "test-pkg", FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "default.profile"}})}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{Source: srcBinary, Destination: ".", Type: "file"}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
true,
"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
pkg, content, dirs := tt.setupFunc(tmpDir)
err := createFirejailedDirectory(dirs.PkgDir)
assert.NoError(t, err)
result, err := createFirejailedBinary(pkg, content, dirs)
if tt.expectError {
assert.Error(t, err)
if tt.errorMsg != "" {
assert.Contains(t, err.Error(), tt.errorMsg)
}
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.Len(t, result, 2)
binContent := result[0]
assert.Contains(t, binContent.Destination, "usr/lib/alr/firejailed/")
assert.FileExists(t, binContent.Source)
profileContent := result[1]
assert.Contains(t, profileContent.Destination, "usr/lib/alr/firejailed/")
assert.Contains(t, profileContent.Destination, ".profile")
assert.FileExists(t, profileContent.Source)
assert.FileExists(t, content.Source)
wrapperBytes, err := os.ReadFile(content.Source)
assert.NoError(t, err)
wrapper := string(wrapperBytes)
assert.Contains(t, wrapper, "#!/bin/bash")
assert.Contains(t, wrapper, "firejail --profile=")
}
})
}
}

View File

@ -17,7 +17,7 @@
package build
import (
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
func NewInstaller(mgr manager.Manager) *Installer {

View File

@ -17,7 +17,7 @@
package build
import (
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
func NewMainBuilder(

View File

@ -28,7 +28,7 @@ import (
"github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
type InstallerPlugin struct {

View File

@ -28,7 +28,7 @@ import (
"github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
var HandshakeConfig = plugin.HandshakeConfig{
@ -50,13 +50,13 @@ type ScriptExecutorRPCServer struct {
// ReadScript
//
func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) {
var resp *ScriptFile
func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
var resp *alrsh.ScriptFile
err := s.client.Call("Plugin.ReadScript", scriptPath, &resp)
return resp, err
}
func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile) error {
func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *alrsh.ScriptFile) error {
file, err := s.Impl.ReadScript(context.Background(), scriptPath)
if err != nil {
return err
@ -72,15 +72,15 @@ func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile
type ExecuteFirstPassArgs struct {
Input *BuildInput
Sf *ScriptFile
Sf *alrsh.ScriptFile
}
type ExecuteFirstPassResp struct {
BasePkg string
VarsOfPackages []*types.BuildVars
VarsOfPackages []*alrsh.Package
}
func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) {
func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
var resp *ExecuteFirstPassResp
err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{
Input: input,
@ -148,29 +148,29 @@ func (s *ScriptExecutorRPCServer) PrepareDirs(args *PrepareDirsArgs, reply *stru
type ExecuteSecondPassArgs struct {
Input *BuildInput
Sf *ScriptFile
VarsOfPackages []*types.BuildVars
Sf *alrsh.ScriptFile
VarsOfPackages []*alrsh.Package
RepoDeps []string
BuiltNames []string
BuiltDeps []*BuiltDep
BasePkg string
}
func (s *ScriptExecutorRPC) ExecuteSecondPass(
ctx context.Context,
input *BuildInput,
sf *ScriptFile,
varsOfPackages []*types.BuildVars,
sf *alrsh.ScriptFile,
varsOfPackages []*alrsh.Package,
repoDeps []string,
builtNames []string,
builtDeps []*BuiltDep,
basePkg string,
) (*SecondPassResult, error) {
var resp *SecondPassResult
) ([]*BuiltDep, error) {
var resp []*BuiltDep
err := s.client.Call("Plugin.ExecuteSecondPass", &ExecuteSecondPassArgs{
Input: input,
Sf: sf,
VarsOfPackages: varsOfPackages,
RepoDeps: repoDeps,
BuiltNames: builtNames,
BuiltDeps: builtDeps,
BasePkg: basePkg,
}, &resp)
if err != nil {
@ -179,20 +179,20 @@ func (s *ScriptExecutorRPC) ExecuteSecondPass(
return resp, nil
}
func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *SecondPassResult) error {
func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *[]*BuiltDep) error {
res, err := s.Impl.ExecuteSecondPass(
context.Background(),
args.Input,
args.Sf,
args.VarsOfPackages,
args.RepoDeps,
args.BuiltNames,
args.BuiltDeps,
args.BasePkg,
)
if err != nil {
return err
}
*resp = *res
*resp = res
return err
}

View File

@ -19,7 +19,6 @@ package build
import (
"bytes"
"context"
"errors"
"fmt"
"log/slog"
"os"
@ -36,12 +35,12 @@ import (
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
finddeps "gitea.plemya-x.ru/Plemya-x/ALR/internal/build/find_deps"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
finddeps "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build/find_deps"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type LocalScriptExecutor struct {
@ -54,107 +53,12 @@ func NewLocalScriptExecutor(cfg Config) *LocalScriptExecutor {
}
}
func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) {
fl, err := readScript(scriptPath)
if err != nil {
return nil, err
}
return &ScriptFile{
Path: scriptPath,
File: fl,
}, nil
func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
return alrsh.ReadFromLocal(scriptPath)
}
func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) {
varsOfPackages := []*types.BuildVars{}
scriptDir := filepath.Dir(sf.Path)
env := createBuildEnvVars(input.info, types.Directories{ScriptDir: scriptDir})
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
interp.StdIO(os.Stdin, os.Stderr, os.Stderr), // Устанавливаем стандартный ввод-вывод
interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение
interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)), // Ограничиваем чтение директорий
interp.StatHandler(handlers.RestrictedStat(scriptDir)), // Ограничиваем доступ к статистике файлов
interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов
interp.Dir(scriptDir),
)
if err != nil {
return "", nil, err
}
err = runner.Run(ctx, sf.File) // Запускаем скрипт
if err != nil {
return "", nil, err
}
dec := decoder.New(input.info, runner) // Создаём новый декодер
type packages struct {
BasePkgName string `sh:"basepkg_name"`
Names []string `sh:"name"`
}
var pkgs packages
err = dec.DecodeVars(&pkgs)
if err != nil {
return "", nil, err
}
if len(pkgs.Names) == 0 {
return "", nil, errors.New("package name is missing")
}
var vars types.BuildVars
if len(pkgs.Names) == 1 {
err = dec.DecodeVars(&vars) // Декодируем переменные
if err != nil {
return "", nil, err
}
varsOfPackages = append(varsOfPackages, &vars)
return vars.Name, varsOfPackages, nil
}
var pkgNames []string
if len(input.packages) != 0 {
pkgNames = input.packages
} else {
pkgNames = pkgs.Names
}
for _, pkgName := range pkgNames {
var preVars types.BuildVarsPre
funcName := fmt.Sprintf("meta_%s", pkgName)
meta, ok := dec.GetFuncWithSubshell(funcName)
if !ok {
return "", nil, fmt.Errorf("func %s is missing", funcName)
}
r, err := meta(ctx)
if err != nil {
return "", nil, err
}
d := decoder.New(&distro.OSRelease{}, r)
err = d.DecodeVars(&preVars)
if err != nil {
return "", nil, err
}
vars := preVars.ToBuildVars()
vars.Name = pkgName
vars.Base = pkgs.BasePkgName
varsOfPackages = append(varsOfPackages, &vars)
}
return pkgs.BasePkgName, varsOfPackages, nil
}
type SecondPassResult struct {
BuiltPaths []string
BuiltNames []string
func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
return sf.ParseBuildVars(ctx, input.info, input.packages)
}
func (e *LocalScriptExecutor) PrepareDirs(
@ -182,13 +86,13 @@ func (e *LocalScriptExecutor) PrepareDirs(
func (e *LocalScriptExecutor) ExecuteSecondPass(
ctx context.Context,
input *BuildInput,
sf *ScriptFile,
varsOfPackages []*types.BuildVars,
sf *alrsh.ScriptFile,
varsOfPackages []*alrsh.Package,
repoDeps []string,
builtNames []string,
builtDeps []*BuiltDep,
basePkg string,
) (*SecondPassResult, error) {
dirs, err := getDirs(e.cfg, sf.Path, basePkg)
) ([]*BuiltDep, error) {
dirs, err := getDirs(e.cfg, sf.Path(), basePkg)
if err != nil {
return nil, err
}
@ -206,14 +110,14 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
return nil, err
}
err = runner.Run(ctx, sf.File)
err = runner.Run(ctx, sf.File())
if err != nil {
return nil, err
}
dec := decoder.New(input.info, runner)
var builtPaths []string
// var builtPaths []string
err = e.ExecuteFunctions(ctx, dirs, dec)
if err != nil {
@ -222,7 +126,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
for _, vars := range varsOfPackages {
packageName := ""
if vars.Base != "" {
if vars.BasePkgName != "" {
packageName = vars.Name
}
@ -247,7 +151,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
dirs,
append(
repoDeps,
builtNames...,
GetBuiltName(builtDeps)...,
),
funcOut.Contents,
)
@ -273,14 +177,13 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
return nil, err
}
builtPaths = append(builtPaths, pkgPath)
builtNames = append(builtNames, vars.Name)
builtDeps = append(builtDeps, &BuiltDep{
Name: vars.Name,
Path: pkgPath,
})
}
return &SecondPassResult{
BuiltPaths: builtPaths,
BuiltNames: builtNames,
}, nil
return builtDeps, nil
}
func buildPkgMetadata(
@ -291,24 +194,24 @@ func buildPkgMetadata(
PkgFormatProvider
RepositoryProvider
},
vars *types.BuildVars,
vars *alrsh.Package,
dirs types.Directories,
deps []string,
preferedContents *[]string,
) (*nfpm.Info, error) {
pkgInfo := getBasePkgInfo(vars, input)
pkgInfo.Description = vars.Description
pkgInfo.Description = vars.Description.Resolved()
pkgInfo.Platform = "linux"
pkgInfo.Homepage = vars.Homepage
pkgInfo.Homepage = vars.Homepage.Resolved()
pkgInfo.License = strings.Join(vars.Licenses, ", ")
pkgInfo.Maintainer = vars.Maintainer
pkgInfo.Maintainer = vars.Maintainer.Resolved()
pkgInfo.Overridables = nfpm.Overridables{
Conflicts: append(vars.Conflicts, vars.Name),
Replaces: vars.Replaces,
Provides: append(vars.Provides, vars.Name),
Depends: deps,
}
pkgInfo.Section = vars.Group
pkgInfo.Section = vars.Group.Resolved()
pkgFormat := input.PkgFormat()
info := input.OSRelease()
@ -321,12 +224,12 @@ func buildPkgMetadata(
}
if pkgFormat == "rpm" {
pkgInfo.RPM.Group = vars.Group
pkgInfo.RPM.Group = vars.Group.Resolved()
if vars.Summary != "" {
pkgInfo.RPM.Summary = vars.Summary
if vars.Summary.Resolved() != "" {
pkgInfo.RPM.Summary = vars.Summary.Resolved()
} else {
lines := strings.SplitN(vars.Description, "\n", 2)
lines := strings.SplitN(vars.Description.Resolved(), "\n", 2)
pkgInfo.RPM.Summary = lines[0]
}
}
@ -345,19 +248,29 @@ func buildPkgMetadata(
if err != nil {
return nil, err
}
pkgInfo.Overridables.Contents = contents
if len(vars.AutoProv) == 1 && decoder.IsTruthy(vars.AutoProv[0]) {
f := finddeps.New(info, pkgFormat)
err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList)
normalizeContents(contents)
if vars.FireJailed.Resolved() {
contents, err = applyFirejailIntegration(vars, dirs, contents)
if err != nil {
return nil, err
}
}
if len(vars.AutoReq) == 1 && decoder.IsTruthy(vars.AutoReq[0]) {
pkgInfo.Overridables.Contents = contents
if len(vars.AutoProv.Resolved()) == 1 && decoder.IsTruthy(vars.AutoProv.Resolved()[0]) {
f := finddeps.New(info, pkgFormat)
err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList)
err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList.Resolved())
if err != nil {
return nil, err
}
}
if len(vars.AutoReq.Resolved()) == 1 && decoder.IsTruthy(vars.AutoReq.Resolved()[0]) {
f := finddeps.New(info, pkgFormat)
err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList.Resolved())
if err != nil {
return nil, err
}

View File

@ -18,9 +18,10 @@ package build
import (
"context"
"os"
"path/filepath"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type ScriptResolver struct {
@ -34,16 +35,27 @@ type ScriptInfo struct {
func (s *ScriptResolver) ResolveScript(
ctx context.Context,
pkg *db.Package,
pkg *alrsh.Package,
) *ScriptInfo {
var repository, script string
repodir := s.cfg.GetPaths().RepoDir
repository = pkg.Repository
if pkg.BasePkgName != "" {
script = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh")
// First, we check if there is a root alr.sh in the repository
rootScriptPath := filepath.Join(repodir, repository, "alr.sh")
if _, err := os.Stat(rootScriptPath); err == nil {
// A repository with a single alr.sh at the root
script = rootScriptPath
} else {
script = filepath.Join(repodir, repository, pkg.Name, "alr.sh")
// Multi-package repository - we are looking for alr.sh in the subfolder
var scriptPath string
if pkg.BasePkgName != "" {
scriptPath = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh")
} else {
scriptPath = filepath.Join(repodir, repository, pkg.Name, "alr.sh")
}
script = scriptPath
}
return &ScriptInfo{

View File

@ -20,6 +20,7 @@ import (
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type ScriptViewerConfig interface {
@ -33,12 +34,12 @@ type ScriptViewer struct {
func (s *ScriptViewer) ViewScript(
ctx context.Context,
input *BuildInput,
sf *ScriptFile,
a *alrsh.ScriptFile,
basePkg string,
) error {
return cliutils.PromptViewScript(
ctx,
sf.Path,
a.Path(),
basePkg,
s.config.PagerStyle(),
input.opts.Interactive,

View File

@ -33,34 +33,18 @@ import (
_ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb"
_ "github.com/goreleaser/nfpm/v2/rpm"
"mvdan.cc/sh/v3/syntax"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"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/internal/types"
"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/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
// Функция readScript анализирует скрипт сборки с использованием встроенной реализации bash
func readScript(script string) (*syntax.File, error) {
fl, err := os.Open(script) // Открываем файл скрипта
if err != nil {
return nil, err
}
defer fl.Close() // Закрываем файл после выполнения
file, err := syntax.NewParser().Parse(fl, "alr.sh") // Парсим скрипт с помощью синтаксического анализатора
if err != nil {
return nil, err
}
return file, nil // Возвращаем синтаксическое дерево
}
// Функция prepareDirs подготавливает директории для сборки.
func prepareDirs(dirs types.Directories) error {
err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует
@ -76,7 +60,7 @@ func prepareDirs(dirs types.Directories) error {
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
// которые будут включены в конечный пакет.
func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
func buildContents(vars *alrsh.Package, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
contents := []*files.Content{}
processPath := func(path, trimmed string, prefered bool) error {
@ -139,7 +123,7 @@ func buildContents(vars *types.BuildVars, dirs types.Directories, preferedConten
},
}
if slices.Contains(vars.Backup, trimmed) {
if slices.Contains(vars.Backup.Resolved(), trimmed) {
fileContent.Type = "config|noreplace"
}
@ -170,9 +154,15 @@ func buildContents(vars *types.BuildVars, dirs types.Directories, preferedConten
return contents, nil
}
func normalizeContents(contents []*files.Content) {
for _, content := range contents {
content.Destination = filepath.Join("/", content.Destination)
}
}
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`)
func getBasePkgInfo(vars *types.BuildVars, input interface {
func getBasePkgInfo(vars *alrsh.Package, input interface {
RepositoryProvider
OsInfoProvider
},
@ -228,39 +218,39 @@ func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string
}
// Функция setScripts добавляет скрипты-перехватчики к метаданным пакета.
func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) {
if vars.Scripts.PreInstall != "" {
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall)
func setScripts(vars *alrsh.Package, info *nfpm.Info, scriptDir string) {
if vars.Scripts.Resolved().PreInstall != "" {
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PreInstall)
}
if vars.Scripts.PostInstall != "" {
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.PostInstall)
if vars.Scripts.Resolved().PostInstall != "" {
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PostInstall)
}
if vars.Scripts.PreRemove != "" {
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.PreRemove)
if vars.Scripts.Resolved().PreRemove != "" {
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PreRemove)
}
if vars.Scripts.PostRemove != "" {
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.PostRemove)
if vars.Scripts.Resolved().PostRemove != "" {
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PostRemove)
}
if vars.Scripts.PreUpgrade != "" {
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
if vars.Scripts.Resolved().PreUpgrade != "" {
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade)
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade)
}
if vars.Scripts.PostUpgrade != "" {
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
if vars.Scripts.Resolved().PostUpgrade != "" {
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade)
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade)
}
if vars.Scripts.PreTrans != "" {
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.PreTrans)
if vars.Scripts.Resolved().PreTrans != "" {
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PreTrans)
}
if vars.Scripts.PostTrans != "" {
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.PostTrans)
if vars.Scripts.Resolved().PostTrans != "" {
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PostTrans)
}
}
@ -288,14 +278,14 @@ func packageNames(pkgs []db.Package) []string {
*/
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
func removeDuplicates(slice []string) []string {
seen := map[string]struct{}{}
result := []string{}
func removeDuplicates[T comparable](slice []T) []T {
seen := map[T]struct{}{}
result := []T{}
for _, s := range slice {
if _, ok := seen[s]; !ok {
seen[s] = struct{}{}
result = append(result, s)
for _, item := range slice {
if _, ok := seen[item]; !ok {
seen[item] = struct{}{}
result = append(result, item)
}
}

View File

@ -26,9 +26,9 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
)
type AppDeps struct {
@ -123,8 +123,15 @@ func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder {
cfg := b.deps.Cfg
db := b.deps.DB
if cfg == nil || db == nil {
b.err = errors.New("config and db are required before initializing repos")
info := b.deps.Info
if info == nil {
b.WithDistroInfo()
info = b.deps.Info
}
if cfg == nil || db == nil || info == nil {
b.err = errors.New("config, db and info are required before initializing repos")
return b
}

View File

@ -28,8 +28,8 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
// YesNoPrompt asks the user a yes or no question, using def as the default answer
@ -102,8 +102,8 @@ func ShowScript(path, name, style string) error {
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
// of packages by prompting the user if multiple packages match.
func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string, interactive bool) []db.Package {
var outPkgs []db.Package
func FlattenPkgs(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool) []alrsh.Package {
var outPkgs []alrsh.Package
for _, pkgs := range found {
if len(pkgs) > 1 && interactive {
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
@ -120,7 +120,7 @@ func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string
}
// PkgPrompt asks the user to choose between multiple packages.
func PkgPrompt(ctx context.Context, options []db.Package, verb string, interactive bool) (db.Package, error) {
func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, interactive bool) (alrsh.Package, error) {
if !interactive {
return options[0], nil
}
@ -138,7 +138,7 @@ func PkgPrompt(ctx context.Context, options []db.Package, verb string, interacti
var choice int
err := survey.AskOne(prompt, &choice)
if err != nil {
return db.Package{}, err
return alrsh.Package{}, err
}
return options[choice], nil

View File

@ -29,7 +29,7 @@ import (
"github.com/pelletier/go-toml/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type ALRConfig struct {

View File

@ -23,41 +23,18 @@ import (
"context"
"log/slog"
"github.com/jmoiron/sqlx"
"github.com/leonelquinteros/gotext"
_ "modernc.org/sqlite"
"xorm.io/xorm"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
// CurrentVersion is the current version of the database.
// The database is reset if its version doesn't match this.
const CurrentVersion = 4
const CurrentVersion = 5
// Package is a ALR package's database representation
type Package struct {
BasePkgName string `sh:"base" db:"basepkg_name"`
Name string `sh:"name,required" db:"name"`
Version string `sh:"version,required" db:"version"`
Release int `sh:"release,required" db:"release"`
Epoch uint `sh:"epoch" db:"epoch"`
Summary JSON[map[string]string] `db:"summary"`
Description JSON[map[string]string] `db:"description"`
Group JSON[map[string]string] `db:"group_name"`
Homepage JSON[map[string]string] `db:"homepage"`
Maintainer JSON[map[string]string] `db:"maintainer"`
Architectures JSON[[]string] `sh:"architectures" db:"architectures"`
Licenses JSON[[]string] `sh:"license" db:"licenses"`
Provides JSON[[]string] `sh:"provides" db:"provides"`
Conflicts JSON[[]string] `sh:"conflicts" db:"conflicts"`
Replaces JSON[[]string] `sh:"replaces" db:"replaces"`
Depends JSON[map[string][]string] `db:"depends"`
BuildDepends JSON[map[string][]string] `db:"builddepends"`
OptDepends JSON[map[string][]string] `db:"optdepends"`
Repository string `db:"repository"`
}
type version struct {
Version int `db:"version"`
type Version struct {
Version int `xorm:"'version'"`
}
type Config interface {
@ -65,7 +42,7 @@ type Config interface {
}
type Database struct {
conn *sqlx.DB
engine *xorm.Engine
config Config
}
@ -75,181 +52,100 @@ func New(config Config) *Database {
}
}
func (d *Database) Init(ctx context.Context) error {
err := d.Connect(ctx)
if err != nil {
return err
}
return d.initDB(ctx)
}
func (d *Database) Connect(ctx context.Context) error {
func (d *Database) Connect() error {
dsn := d.config.GetPaths().DBPath
db, err := sqlx.Open("sqlite", dsn)
engine, err := xorm.NewEngine("sqlite", dsn)
// engine.SetLogLevel(log.LOG_DEBUG)
// engine.ShowSQL(true)
if err != nil {
return err
}
d.conn = db
d.engine = engine
return nil
}
func (d *Database) GetConn() *sqlx.DB {
return d.conn
}
func (d *Database) initDB(ctx context.Context) error {
d.conn = d.conn.Unsafe()
conn := d.conn
_, err := conn.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS pkgs (
basepkg_name TEXT NOT NULL,
name TEXT NOT NULL,
repository TEXT NOT NULL,
version TEXT NOT NULL,
release INT NOT NULL,
epoch INT,
summary TEXT CHECK(summary = 'null' OR (JSON_VALID(summary) AND JSON_TYPE(summary) = 'object')),
description TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')),
group_name TEXT CHECK(group_name = 'null' OR (JSON_VALID(group_name) AND JSON_TYPE(group_name) = 'object')),
homepage TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')),
maintainer TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')),
architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')),
licenses TEXT CHECK(licenses = 'null' OR (JSON_VALID(licenses) AND JSON_TYPE(licenses) = 'array')),
provides TEXT CHECK(provides = 'null' OR (JSON_VALID(provides) AND JSON_TYPE(provides) = 'array')),
conflicts TEXT CHECK(conflicts = 'null' OR (JSON_VALID(conflicts) AND JSON_TYPE(conflicts) = 'array')),
replaces TEXT CHECK(replaces = 'null' OR (JSON_VALID(replaces) AND JSON_TYPE(replaces) = 'array')),
depends TEXT CHECK(depends = 'null' OR (JSON_VALID(depends) AND JSON_TYPE(depends) = 'object')),
builddepends TEXT CHECK(builddepends = 'null' OR (JSON_VALID(builddepends) AND JSON_TYPE(builddepends) = 'object')),
optdepends TEXT CHECK(optdepends = 'null' OR (JSON_VALID(optdepends) AND JSON_TYPE(optdepends) = 'object')),
UNIQUE(name, repository)
);
CREATE TABLE IF NOT EXISTS alr_db_version (
version INT NOT NULL
);
`)
if err != nil {
func (d *Database) Init(ctx context.Context) error {
if err := d.Connect(); err != nil {
return err
}
if err := d.engine.Sync2(new(alrsh.Package), new(Version)); err != nil {
return err
}
ver, ok := d.GetVersion(ctx)
if ok && ver != CurrentVersion {
slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion)
err = d.reset(ctx)
if err != nil {
if err := d.reset(); err != nil {
return err
}
return d.initDB(ctx)
return d.Init(ctx)
} else if !ok {
slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion)
return d.addVersion(ctx, CurrentVersion)
slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."))
return d.addVersion(CurrentVersion)
}
return nil
}
func (d *Database) GetVersion(ctx context.Context) (int, bool) {
var ver version
err := d.conn.GetContext(ctx, &ver, "SELECT * FROM alr_db_version LIMIT 1;")
if err != nil {
var v Version
has, err := d.engine.Get(&v)
if err != nil || !has {
return 0, false
}
return ver.Version, true
return v.Version, true
}
func (d *Database) addVersion(ctx context.Context, ver int) error {
_, err := d.conn.ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver)
func (d *Database) addVersion(ver int) error {
_, err := d.engine.Insert(&Version{Version: ver})
return err
}
func (d *Database) reset(ctx context.Context) error {
_, err := d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;")
func (d *Database) reset() error {
return d.engine.DropTables(new(alrsh.Package), new(Version))
}
func (d *Database) InsertPackage(ctx context.Context, pkg alrsh.Package) error {
session := d.engine.Context(ctx)
affected, err := session.Where("name = ? AND repository = ?", pkg.Name, pkg.Repository).Update(&pkg)
if err != nil {
return err
}
_, err = d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS alr_db_version;")
return err
if affected == 0 {
_, err = session.Insert(&pkg)
if err != nil {
return err
}
}
return nil
}
func (d *Database) GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) {
stream, err := d.conn.QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...)
if err != nil {
func (d *Database) GetPkgs(_ context.Context, where string, args ...any) ([]alrsh.Package, error) {
var pkgs []alrsh.Package
err := d.engine.Where(where, args...).Find(&pkgs)
return pkgs, err
}
func (d *Database) GetPkg(where string, args ...any) (*alrsh.Package, error) {
var pkg alrsh.Package
has, err := d.engine.Where(where, args...).Get(&pkg)
if err != nil || !has {
return nil, err
}
return stream, nil
return &pkg, nil
}
func (d *Database) GetPkg(ctx context.Context, where string, args ...any) (*Package, error) {
out := &Package{}
err := d.conn.GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
return out, err
}
func (d *Database) DeletePkgs(ctx context.Context, where string, args ...any) error {
_, err := d.conn.ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...)
func (d *Database) DeletePkgs(_ context.Context, where string, args ...any) error {
_, err := d.engine.Where(where, args...).Delete(&alrsh.Package{})
return err
}
func (d *Database) IsEmpty(ctx context.Context) bool {
var count int
err := d.conn.GetContext(ctx, &count, "SELECT count(1) FROM pkgs;")
if err != nil {
return true
}
return count == 0
}
func (d *Database) InsertPackage(ctx context.Context, pkg Package) error {
_, err := d.conn.NamedExecContext(ctx, `
INSERT OR REPLACE INTO pkgs (
basepkg_name,
name,
repository,
version,
release,
epoch,
summary,
description,
group_name,
homepage,
maintainer,
architectures,
licenses,
provides,
conflicts,
replaces,
depends,
builddepends,
optdepends
) VALUES (
:basepkg_name,
:name,
:repository,
:version,
:release,
:epoch,
:summary,
:description,
:group_name,
:homepage,
:maintainer,
:architectures,
:licenses,
:provides,
:conflicts,
:replaces,
:depends,
:builddepends,
:optdepends
);
`, pkg)
return err
func (d *Database) IsEmpty() bool {
count, err := d.engine.Count(new(alrsh.Package))
return err != nil || count == 0
}
func (d *Database) Close() error {
if d.conn != nil {
return d.conn.Close()
} else {
return nil
}
return d.engine.Close()
}

View File

@ -25,10 +25,11 @@ import (
"strings"
"testing"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type TestALRConfig struct{}
@ -45,35 +46,38 @@ func prepareDb() *db.Database {
return database
}
var testPkg = db.Package{
var testPkg = alrsh.Package{
Name: "test",
Version: "0.0.1",
Release: 1,
Epoch: 2,
Description: db.NewJSON(map[string]string{
Description: alrsh.OverridableFromMap(map[string]string{
"en": "Test package",
"ru": "Проверочный пакет",
}),
Homepage: db.NewJSON(map[string]string{
Homepage: alrsh.OverridableFromMap(map[string]string{
"en": "https://gitea.plemya-x.ru/xpamych/ALR",
}),
Maintainer: db.NewJSON(map[string]string{
Maintainer: alrsh.OverridableFromMap(map[string]string{
"en": "Evgeniy Khramov <xpamych@yandex.ru>",
"ru": "Евгений Храмов <xpamych@yandex.ru>",
}),
Architectures: db.NewJSON([]string{"arm64", "amd64"}),
Licenses: db.NewJSON([]string{"GPL-3.0-or-later"}),
Provides: db.NewJSON([]string{"test"}),
Conflicts: db.NewJSON([]string{"test"}),
Replaces: db.NewJSON([]string{"test-old"}),
Depends: db.NewJSON(map[string][]string{
Architectures: []string{"arm64", "amd64"},
Licenses: []string{"GPL-3.0-or-later"},
Provides: []string{"test"},
Conflicts: []string{"test"},
Replaces: []string{"test-old"},
Depends: alrsh.OverridableFromMap(map[string][]string{
"": {"sudo"},
}),
BuildDepends: db.NewJSON(map[string][]string{
BuildDepends: alrsh.OverridableFromMap(map[string][]string{
"": {"golang"},
"arch": {"go"},
}),
Repository: "default",
Summary: alrsh.OverridableFromMap(map[string]string{}),
Group: alrsh.OverridableFromMap(map[string]string{}),
OptDepends: alrsh.OverridableFromMap(map[string][]string{}),
}
func TestInit(t *testing.T) {
@ -99,15 +103,16 @@ func TestInsertPackage(t *testing.T) {
t.Fatalf("Expected no error, got %s", err)
}
dbPkg := db.Package{}
err = sqlx.Get(database.GetConn(), &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'")
pkgs, err := database.GetPkgs(ctx, "name = 'test' AND repository = 'default'")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
if !reflect.DeepEqual(testPkg, dbPkg) {
t.Errorf("Expected test package to be the same as database package")
if len(pkgs) != 1 {
t.Fatalf("Expected 1 package, got %d", len(pkgs))
}
assert.Equal(t, testPkg, pkgs[0])
}
func TestGetPkgs(t *testing.T) {
@ -130,18 +135,12 @@ func TestGetPkgs(t *testing.T) {
t.Errorf("Expected no error, got %s", err)
}
result, err := database.GetPkgs(ctx, "name LIKE 'x%'")
pkgs, err := database.GetPkgs(ctx, "name LIKE 'x%'")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
for result.Next() {
var dbPkg db.Package
err = result.StructScan(&dbPkg)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
for _, dbPkg := range pkgs {
if !strings.HasPrefix(dbPkg.Name, "x") {
t.Errorf("Expected package name to start with 'x', got %s", dbPkg.Name)
}
@ -168,7 +167,7 @@ func TestGetPkg(t *testing.T) {
t.Errorf("Expected no error, got %s", err)
}
pkg, err := database.GetPkg(ctx, "name LIKE 'x%' ORDER BY name")
pkg, err := database.GetPkg("name LIKE 'x%'")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
@ -206,16 +205,6 @@ func TestDeletePkgs(t *testing.T) {
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
var dbPkg db.Package
err = database.GetConn().Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;")
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
if dbPkg.Name != "x2" {
t.Errorf("Expected x2 package, got %s", dbPkg.Name)
}
}
func TestJsonArrayContains(t *testing.T) {
@ -227,7 +216,7 @@ func TestJsonArrayContains(t *testing.T) {
x1.Name = "x1"
x2 := testPkg
x2.Name = "x2"
x2.Provides.Val = append(x2.Provides.Val, "x")
x2.Provides = append(x2.Provides, "x")
err := database.InsertPackage(ctx, x1)
if err != nil {
@ -239,13 +228,24 @@ func TestJsonArrayContains(t *testing.T) {
t.Errorf("Expected no error, got %s", err)
}
var dbPkg db.Package
err = database.GetConn().Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');")
pkgs, err := database.GetPkgs(ctx, "name = 'x2'")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
if dbPkg.Name != "x2" {
t.Errorf("Expected x2 package, got %s", dbPkg.Name)
if len(pkgs) != 1 || pkgs[0].Name != "x2" {
t.Errorf("Expected x2 package, got %v", pkgs)
}
// Verify the provides field contains 'x'
found := false
for _, p := range pkgs[0].Provides {
if p == "x" {
found = true
break
}
}
if !found {
t.Errorf("Expected provides to contain 'x'")
}
}

View File

@ -1,80 +0,0 @@
// 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 db
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
)
// JSON represents a JSON value in the database
type JSON[T any] struct {
Val T
}
// NewJSON creates a new database JSON value
func NewJSON[T any](v T) JSON[T] {
return JSON[T]{Val: v}
}
func (s *JSON[T]) Scan(val any) error {
if val == nil {
return nil
}
switch val := val.(type) {
case string:
err := json.Unmarshal([]byte(val), &s.Val)
if err != nil {
return err
}
case sql.NullString:
if val.Valid {
err := json.Unmarshal([]byte(val.String), &s.Val)
if err != nil {
return err
}
}
default:
return errors.New("sqlite json types must be strings")
}
return nil
}
func (s JSON[T]) Value() (driver.Value, error) {
data, err := json.Marshal(s.Val)
if err != nil {
return nil, err
}
return string(data), nil
}
func (s JSON[T]) MarshalYAML() (any, error) {
return s.Val, nil
}
func (s JSON[T]) String() string {
return fmt.Sprint(s.Val)
}
func (s JSON[T]) GoString() string {
return fmt.Sprintf("%#v", s.Val)
}

View File

@ -67,7 +67,7 @@ func (a *APTRpm) Sync(opts *Opts) error {
func (a *APTRpm) Install(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := a.getCmd(opts, "apt-get", "install")
cmd := a.getCmd(opts, "apt-get", "install", "-o", "APT::Install::Virtual=true")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
cmd.Stdout = cmd.Stderr

View File

@ -21,7 +21,6 @@ package overrides
import (
"fmt"
"reflect"
"regexp"
"strings"
@ -29,7 +28,6 @@ import (
"golang.org/x/text/language"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
@ -150,65 +148,6 @@ func (o *Opts) WithLanguageTags(langs []string) *Opts {
return out
}
// ResolvedPackage is a ALR package after its overrides
// have been resolved
type ResolvedPackage struct {
Name string `sh:"name"`
Version string `sh:"version"`
Release int `sh:"release"`
Epoch uint `sh:"epoch"`
Group string `db:"group_name"`
Summary string `db:"summary"`
Description string `db:"description"`
Homepage string `db:"homepage"`
Maintainer string `db:"maintainer"`
Architectures []string `sh:"architectures"`
Licenses []string `sh:"license"`
Provides []string `sh:"provides"`
Conflicts []string `sh:"conflicts"`
Replaces []string `sh:"replaces"`
Depends []string `sh:"deps"`
BuildDepends []string `sh:"build_deps"`
OptDepends []string `sh:"opt_deps"`
}
func ResolvePackage(pkg *db.Package, overrides []string) *ResolvedPackage {
out := &ResolvedPackage{}
outVal := reflect.ValueOf(out).Elem()
pkgVal := reflect.ValueOf(pkg).Elem()
for i := 0; i < outVal.NumField(); i++ {
fieldVal := outVal.Field(i)
fieldType := fieldVal.Type()
pkgFieldVal := pkgVal.FieldByName(outVal.Type().Field(i).Name)
pkgFieldType := pkgFieldVal.Type()
if strings.HasPrefix(pkgFieldType.String(), "db.JSON") {
pkgFieldVal = pkgFieldVal.FieldByName("Val")
pkgFieldType = pkgFieldVal.Type()
}
if pkgFieldType.AssignableTo(fieldType) {
fieldVal.Set(pkgFieldVal)
continue
}
if pkgFieldVal.Kind() == reflect.Map && pkgFieldType.Elem().AssignableTo(fieldType) {
for _, override := range overrides {
overrideVal := pkgFieldVal.MapIndex(reflect.ValueOf(override))
if !overrideVal.IsValid() {
continue
}
fieldVal.Set(overrideVal)
break
}
}
}
return out
}
func parseLangs(langs []string, tags []language.Tag) ([]string, error) {
out := make([]string, len(tags)+len(langs))
for i, tag := range tags {

View File

@ -22,11 +22,11 @@ package repos
import (
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
found := map[string][]db.Package{}
func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error) {
found := map[string][]alrsh.Package{}
notFound := []string(nil)
for _, pkgName := range pkgs {
@ -40,17 +40,10 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.P
}
added := 0
for result.Next() {
var pkg db.Package
err = result.StructScan(&pkg)
if err != nil {
return nil, nil, err
}
for _, pkg := range result {
added++
found[pkgName] = append(found[pkgName], pkg)
}
result.Close()
if added == 0 {
result, err := rs.db.GetPkgs(ctx, "name LIKE ?", pkgName)
@ -58,18 +51,10 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.P
return nil, nil, err
}
for result.Next() {
var pkg db.Package
err = result.StructScan(&pkg)
if err != nil {
return nil, nil, err
}
for _, pkg := range result {
added++
found[pkgName] = append(found[pkgName], pkg)
}
result.Close()
}
if added == 0 {

View File

@ -24,9 +24,9 @@ import (
"strings"
"testing"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
func TestFindPkgs(t *testing.T) {
@ -41,7 +41,7 @@ func TestFindPkgs(t *testing.T) {
err := rs.Pull(e.Ctx, []types.Repo{
{
Name: "default",
URL: "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git",
URL: "https://gitea.plemya-x.ru/Plemya-x/alr-default.git",
},
})
if err != nil {
@ -89,31 +89,31 @@ func TestFindPkgsEmpty(t *testing.T) {
e.Db,
)
err := e.Db.InsertPackage(e.Ctx, db.Package{
err := e.Db.InsertPackage(e.Ctx, alrsh.Package{
Name: "test1",
Repository: "default",
Version: "0.0.1",
Release: 1,
Description: db.NewJSON(map[string]string{
Provides: []string{""},
Description: alrsh.OverridableFromMap(map[string]string{
"en": "Test package 1",
"ru": "Проверочный пакет 1",
}),
Provides: db.NewJSON([]string{""}),
})
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
err = e.Db.InsertPackage(e.Ctx, db.Package{
err = e.Db.InsertPackage(e.Ctx, alrsh.Package{
Name: "test2",
Repository: "default",
Version: "0.0.1",
Release: 1,
Description: db.NewJSON(map[string]string{
Provides: []string{"test"},
Description: alrsh.OverridableFromMap(map[string]string{
"en": "Test package 2",
"ru": "Проверочный пакет 2",
}),
Provides: db.NewJSON([]string{"test"}),
})
if err != nil {
t.Fatalf("Expected no error, got %s", err)

View File

@ -44,7 +44,7 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type actionType uint8
@ -130,7 +130,7 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
// If the DB was not present at startup, that means it's
// empty. In this case, we need to update the DB fully
// rather than just incrementally.
if rs.db.IsEmpty(ctx) {
if rs.db.IsEmpty() {
err = rs.processRepoFull(ctx, repo, repoDir)
if err != nil {
return err
@ -277,7 +277,15 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
for _, fp := range patch.FilePatches() {
from, to := fp.Files()
if !isValid(from, to) {
var isValidPath bool
if from != nil {
isValidPath = isValidScriptPath(from.Path())
}
if to != nil {
isValidPath = isValidPath || isValidScriptPath(to.Path())
}
if !isValidPath {
continue
}
@ -316,55 +324,60 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
parser := syntax.NewParser()
for _, action := range actions {
runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(filepath.Join(repoDir, action.File)))
var scriptDir string
if filepath.Dir(action.File) == "." {
scriptDir = repoDir
} else {
scriptDir = filepath.Dir(filepath.Join(repoDir, action.File))
}
runner, err := rs.processRepoChangesRunner(repoDir, scriptDir)
if err != nil {
return fmt.Errorf("error creating process repo changes runner: %w", err)
}
switch action.Type {
case actionDelete:
if filepath.Base(action.File) != "alr.sh" {
continue
}
scriptFl, err := oldCommit.File(action.File)
if err != nil {
return nil
slog.Warn("Failed to get deleted file from old commit", "file", action.File, "error", err)
continue
}
r, err := scriptFl.Reader()
if err != nil {
return nil
slog.Warn("Failed to read deleted file", "file", action.File, "error", err)
continue
}
pkgs, err := parseScript(ctx, repo, parser, runner, r)
if err != nil {
return err
return fmt.Errorf("error parsing deleted script %s: %w", action.File, err)
}
for _, pkg := range pkgs {
err = rs.db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name)
if err != nil {
return err
return fmt.Errorf("error deleting package %s: %w", pkg.Name, err)
}
}
case actionUpdate:
if filepath.Base(action.File) != "alr.sh" {
action.File = filepath.Join(filepath.Dir(action.File), "alr.sh")
}
case actionUpdate:
scriptFl, err := newCommit.File(action.File)
if err != nil {
return nil
slog.Warn("Failed to get updated file from new commit", "file", action.File, "error", err)
continue
}
r, err := scriptFl.Reader()
if err != nil {
return nil
slog.Warn("Failed to read updated file", "file", action.File, "error", err)
continue
}
err = rs.updatePkg(ctx, repo, runner, r)
if err != nil {
return fmt.Errorf("error updatePkg: %w", err)
return fmt.Errorf("error updating package from %s: %w", action.File, err)
}
}
}
@ -372,27 +385,68 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
return nil
}
func isValidScriptPath(path string) bool {
if filepath.Base(path) != "alr.sh" {
return false
}
dir := filepath.Dir(path)
return dir == "." || !strings.Contains(strings.TrimPrefix(dir, "./"), "/")
}
func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error {
glob := filepath.Join(repoDir, "/*/alr.sh")
rootScript := filepath.Join(repoDir, "alr.sh")
if fi, err := os.Stat(rootScript); err == nil && !fi.IsDir() {
slog.Debug("Found root alr.sh, processing single-script repository", "repo", repo.Name)
runner, err := rs.processRepoChangesRunner(repoDir, repoDir)
if err != nil {
return fmt.Errorf("error creating runner for root alr.sh: %w", err)
}
scriptFl, err := os.Open(rootScript)
if err != nil {
return fmt.Errorf("error opening root alr.sh: %w", err)
}
defer scriptFl.Close()
err = rs.updatePkg(ctx, repo, runner, scriptFl)
if err != nil {
return fmt.Errorf("error processing root alr.sh: %w", err)
}
return nil
}
glob := filepath.Join(repoDir, "*/alr.sh")
matches, err := filepath.Glob(glob)
if err != nil {
return err
return fmt.Errorf("error globbing for alr.sh files: %w", err)
}
if len(matches) == 0 {
slog.Warn("No alr.sh files found in repository", "repo", repo.Name)
return nil
}
slog.Debug("Found multiple alr.sh files, processing multi-package repository",
"repo", repo.Name, "count", len(matches))
for _, match := range matches {
runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match))
if err != nil {
return err
return fmt.Errorf("error creating runner for %s: %w", match, err)
}
scriptFl, err := os.Open(match)
if err != nil {
return err
return fmt.Errorf("error opening %s: %w", match, err)
}
err = rs.updatePkg(ctx, repo, runner, scriptFl)
scriptFl.Close()
if err != nil {
return err
return fmt.Errorf("error processing %s: %w", match, err)
}
}

View File

@ -27,7 +27,8 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type TestALRConfig struct{}
@ -84,16 +85,10 @@ build_deps=('golang')
result, err := database.GetPkgs(ctx, "1 = 1")
assert.NoError(t, err)
pkgCount := 0
for result.Next() {
var dbPkg db.Package
err = result.StructScan(&dbPkg)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
assert.Equal(t, "foo", dbPkg.Name)
assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description)
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends)
for _, pkg := range result {
assert.Equal(t, "foo", pkg.Name)
assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "main desc"}), pkg.Description)
assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo"}}), pkg.Depends)
pkgCount++
}
assert.Equal(t, 1, pkgCount)
@ -125,20 +120,18 @@ meta_buz() {
assert.NoError(t, err)
pkgCount := 0
for result.Next() {
var dbPkg db.Package
err = result.StructScan(&dbPkg)
for _, pkg := range result {
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
if dbPkg.Name == "bar" {
assert.Equal(t, db.NewJSON(map[string]string{"": "foo desc"}), dbPkg.Description)
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends)
if pkg.Name == "bar" {
assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "foo desc"}), pkg.Description)
assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo"}}), pkg.Depends)
}
if dbPkg.Name == "buz" {
assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description)
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo", "doas"}}), dbPkg.Depends)
if pkg.Name == "buz" {
assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "main desc"}), pkg.Description)
assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo", "doas"}}), pkg.Depends)
}
pkgCount++
}

View File

@ -28,8 +28,8 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type TestEnv struct {
@ -129,15 +129,7 @@ func TestPull(t *testing.T) {
t.Fatalf("Expected no error, got %s", err)
}
var pkgAmt int
for result.Next() {
var dbPkg db.Package
err = result.StructScan(&dbPkg)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
pkgAmt++
}
pkgAmt := len(result)
if pkgAmt == 0 {
t.Errorf("Expected at least 1 matching package, but got %d", pkgAmt)

View File

@ -19,7 +19,7 @@ package repos
import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type Config interface {

112
internal/repos/utils.go Normal file
View File

@ -0,0 +1,112 @@
// 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 repos
import (
"context"
"fmt"
"io"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/client"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
"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"
)
func parseScript(
ctx context.Context,
repo types.Repo,
syntaxParser *syntax.Parser,
runner *interp.Runner,
r io.ReadCloser,
) ([]*alrsh.Package, error) {
f, err := alrsh.ReadFromIOReader(r, "/tmp")
if err != nil {
return nil, err
}
_, dbPkgs, err := f.ParseBuildVars(ctx, &distro.OSRelease{}, []string{})
if err != nil {
return nil, err
}
for _, pkg := range dbPkgs {
pkg.Repository = repo.Name
}
return dbPkgs, nil
}
func getHeadReference(r *git.Repository) (plumbing.ReferenceName, error) {
remote, err := r.Remote(git.DefaultRemoteName)
if err != nil {
return "", err
}
endpoint, err := transport.NewEndpoint(remote.Config().URLs[0])
if err != nil {
return "", err
}
gitClient, err := client.NewClient(endpoint)
if err != nil {
return "", err
}
session, err := gitClient.NewUploadPackSession(endpoint, nil)
if err != nil {
return "", err
}
info, err := session.AdvertisedReferences()
if err != nil {
return "", err
}
refs, err := info.AllReferences()
if err != nil {
return "", err
}
return refs["HEAD"].Target(), nil
}
func resolveHash(r *git.Repository, ref string) (*plumbing.Hash, error) {
var err error
if ref == "" {
reference, err := getHeadReference(r)
if err != nil {
return nil, fmt.Errorf("failed to get head reference %w", err)
}
ref = reference.Short()
}
hsh, err := r.ResolveRevision(git.DefaultRemoteName + "/" + plumbing.Revision(ref))
if err != nil {
hsh, err = r.ResolveRevision(plumbing.Revision(ref))
if err != nil {
return nil, err
}
}
return hsh, nil
}

View File

@ -22,13 +22,11 @@ package search
import (
"context"
"github.com/jmoiron/sqlx"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type PackagesProvider interface {
GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error)
GetPkgs(ctx context.Context, where string, args ...any) ([]alrsh.Package, error)
}
type Searcher struct {
@ -44,23 +42,8 @@ func New(pp PackagesProvider) *Searcher {
func (s *Searcher) Search(
ctx context.Context,
opts *SearchOptions,
) ([]database.Package, error) {
var packages []database.Package
) ([]alrsh.Package, error) {
where, args := opts.WhereClause()
result, err := s.pp.GetPkgs(ctx, where, args...)
if err != nil {
return nil, err
}
for result.Next() {
var dbPkg database.Package
err = result.StructScan(&dbPkg)
if err != nil {
return nil, err
}
packages = append(packages, dbPkg)
}
return packages, nil
packages, err := s.pp.GetPkgs(ctx, where, args...)
return packages, err
}

View File

@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/search"
)
func TestSearhOptionsBuilder(t *testing.T) {

View File

@ -22,6 +22,8 @@ package decoder
import (
"context"
"errors"
"fmt"
"log/slog"
"reflect"
"strings"
@ -52,7 +54,7 @@ type InvalidTypeError struct {
}
func (ite InvalidTypeError) Error() string {
return "variable '" + ite.name + "' is of type " + ite.vartype + ", but " + ite.exptype + " is expected"
return fmt.Sprintf("variable '%s' is of type %s, but %s is expected", ite.name, ite.vartype, ite.exptype)
}
// Decoder provides methods for decoding variable values
@ -81,13 +83,48 @@ func (d *Decoder) DecodeVar(name string, val any) error {
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
WeaklyTypedInput: true,
DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) {
if strings.Contains(to.Type().String(), "db.JSON") {
valType := to.FieldByName("Val").Type()
if !from.Type().AssignableTo(valType) {
return nil, InvalidTypeError{name, from.Type().String(), valType.String()}
if strings.Contains(to.Type().String(), "alrsh.OverridableField") {
if to.Kind() != reflect.Ptr && to.CanAddr() {
to = to.Addr()
}
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
if err != nil {
return nil, err
}
isNotSet := true
setMethod := to.MethodByName("Set")
setResolvedMethod := to.MethodByName("SetResolved")
for _, varName := range names {
val := d.getVarNoOverrides(varName)
if val == nil {
continue
}
t := setMethod.Type().In(1)
newVal := from
if !newVal.Type().AssignableTo(t) {
newVal = reflect.New(t)
err = d.DecodeVar(name, newVal.Interface())
if err != nil {
return nil, err
}
newVal = newVal.Elem()
}
if isNotSet {
setResolvedMethod.Call([]reflect.Value{newVal})
}
override := strings.TrimPrefix(strings.TrimPrefix(varName, name), "_")
setMethod.Call([]reflect.Value{reflect.ValueOf(override), newVal})
}
to.FieldByName("Val").Set(from)
return to, nil
}
return from.Interface(), nil
@ -96,6 +133,7 @@ func (d *Decoder) DecodeVar(name string, val any) error {
TagName: "sh",
})
if err != nil {
slog.Warn("err", "err", err)
return err
}
@ -255,23 +293,31 @@ func (d *Decoder) getVar(name string) *expand.Variable {
}
for _, varName := range names {
val, ok := d.Runner.Vars[varName]
if ok {
// Resolve nameref variables
_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string {
if val, ok := d.Runner.Vars[s]; ok {
return val.String()
}
return ""
}))
val = resolved
return &val
res := d.getVarNoOverrides(varName)
if res != nil {
return res
}
}
return nil
}
func (d *Decoder) getVarNoOverrides(name string) *expand.Variable {
val, ok := d.Runner.Vars[name]
if ok {
// Resolve nameref variables
_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string {
if val, ok := d.Runner.Vars[s]; ok {
return val.String()
}
return ""
}))
val = resolved
return &val
}
return nil
}
func IsTruthy(value string) bool {
value = strings.ToLower(strings.TrimSpace(value))
return value == "true" || value == "yes" || value == "1"

View File

@ -58,11 +58,11 @@ msgstr ""
msgid "Done"
msgstr ""
#: fix.go:38
#: fix.go:39
msgid "Attempt to fix problems with ALR"
msgstr ""
#: fix.go:59
#: fix.go:60
msgid "Clearing cache directory"
msgstr ""
@ -74,15 +74,15 @@ msgstr ""
msgid "Unable to read cache directory contents"
msgstr ""
#: fix.go:76
#: fix.go:82
msgid "Unable to remove cache item (%s)"
msgstr ""
#: fix.go:80
#: fix.go:86
msgid "Rebuilding cache"
msgstr ""
#: fix.go:84
#: fix.go:90
msgid "Unable to create new cache directory"
msgstr ""
@ -126,58 +126,120 @@ msgstr ""
msgid "Error getting packages"
msgstr ""
#: info.go:76
msgid "Error iterating over packages"
msgstr ""
#: info.go:90
#: info.go:83
msgid "Command info expected at least 1 argument, got %d"
msgstr ""
#: info.go:110
#: info.go:104
msgid "Error finding packages"
msgstr ""
#: info.go:124
#: info.go:118
msgid "Can't detect system language"
msgstr ""
#: info.go:141
#: info.go:135
msgid "Error resolving overrides"
msgstr ""
#: info.go:149 info.go:154
#: info.go:144 info.go:149
msgid "Error encoding script variables"
msgstr ""
#: install.go:40
#: install.go:39
msgid "Install a new package"
msgstr ""
#: install.go:52
#: install.go:51
msgid "Command install expected at least 1 argument, got %d"
msgstr ""
#: install.go:114
#: install.go:113
msgid "Error when installing the package"
msgstr ""
#: install.go:159
#: install.go:151
msgid "Remove an installed package"
msgstr ""
#: install.go:178
#: install.go:170
msgid "Error listing installed packages"
msgstr ""
#: install.go:215
#: install.go:199
msgid "Command remove expected at least 1 argument, got %d"
msgstr ""
#: install.go:230
#: install.go:214
msgid "Error removing packages"
msgstr ""
#: internal/build/build.go:376
msgid "Building package"
msgstr ""
#: internal/build/build.go:405
msgid "The checksums array must be the same length as sources"
msgstr ""
#: internal/build/build.go:447
msgid "Downloading sources"
msgstr ""
#: internal/build/build.go:539
msgid "Installing dependencies"
msgstr ""
#: internal/build/checker.go:43
msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?"
msgstr ""
#: internal/build/checker.go:67
msgid "This package is already installed"
msgstr ""
#: internal/build/find_deps/alt_linux.go:35
msgid "Command not found on the system"
msgstr ""
#: internal/build/find_deps/alt_linux.go:86
msgid "Provided dependency found"
msgstr ""
#: internal/build/find_deps/alt_linux.go:93
msgid "Required dependency found"
msgstr ""
#: internal/build/find_deps/empty.go:32
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
#: internal/build/find_deps/empty.go:37
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
#: internal/build/firejail.go:59
msgid "Applying FireJail integration"
msgstr ""
#: internal/build/script_executor.go:145
msgid "Building package metadata"
msgstr ""
#: internal/build/script_executor.go:285
msgid "Executing prepare()"
msgstr ""
#: internal/build/script_executor.go:294
msgid "Executing build()"
msgstr ""
#: internal/build/script_executor.go:323 internal/build/script_executor.go:343
msgid "Executing %s()"
msgstr ""
#: internal/cliutils/app_builder/builder.go:75
msgid "Error loading config"
msgstr ""
@ -186,15 +248,15 @@ msgstr ""
msgid "Error initialization database"
msgstr ""
#: internal/cliutils/app_builder/builder.go:135
#: internal/cliutils/app_builder/builder.go:142
msgid "Error pulling repositories"
msgstr ""
#: internal/cliutils/app_builder/builder.go:152
#: internal/cliutils/app_builder/builder.go:159
msgid "Error parsing os release"
msgstr ""
#: internal/cliutils/app_builder/builder.go:165
#: internal/cliutils/app_builder/builder.go:172
msgid "Unable to detect a supported package manager on the system"
msgstr ""
@ -284,11 +346,11 @@ msgid ""
"instead!"
msgstr ""
#: internal/db/db.go:137
#: internal/db/db.go:76
msgid "Database version mismatch; resetting"
msgstr ""
#: internal/db/db.go:144
#: internal/db/db.go:82
msgid ""
"Database version does not exist. Run alr fix if something isn't working."
msgstr ""
@ -321,6 +383,24 @@ msgstr ""
msgid "ERROR"
msgstr ""
#: internal/repos/pull.go:77
msgid "Pulling repository"
msgstr ""
#: internal/repos/pull.go:113
msgid "Repository up to date"
msgstr ""
#: internal/repos/pull.go:204
msgid "Git repository does not appear to be a valid ALR repo"
msgstr ""
#: internal/repos/pull.go:220
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
msgstr ""
#: internal/utils/cmd.go:97
msgid "Error on dropping capabilities"
msgstr ""
@ -349,11 +429,11 @@ msgstr ""
msgid "No packages for upgrade"
msgstr ""
#: list.go:102 list.go:187
#: list.go:102 list.go:184
msgid "Error parsing format template"
msgstr ""
#: list.go:108 list.go:191
#: list.go:108 list.go:188
msgid "Error executing template"
msgstr ""
@ -377,86 +457,6 @@ msgstr ""
msgid "Error while running app"
msgstr ""
#: pkg/build/build.go:395
msgid "Building package"
msgstr ""
#: pkg/build/build.go:424
msgid "The checksums array must be the same length as sources"
msgstr ""
#: pkg/build/build.go:455
msgid "Downloading sources"
msgstr ""
#: pkg/build/build.go:549
msgid "Installing dependencies"
msgstr ""
#: pkg/build/checker.go:43
msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?"
msgstr ""
#: pkg/build/checker.go:67
msgid "This package is already installed"
msgstr ""
#: pkg/build/find_deps/alt_linux.go:35
msgid "Command not found on the system"
msgstr ""
#: pkg/build/find_deps/alt_linux.go:86
msgid "Provided dependency found"
msgstr ""
#: pkg/build/find_deps/alt_linux.go:93
msgid "Required dependency found"
msgstr ""
#: pkg/build/find_deps/empty.go:32
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/find_deps/empty.go:37
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/script_executor.go:241
msgid "Building package metadata"
msgstr ""
#: pkg/build/script_executor.go:372
msgid "Executing prepare()"
msgstr ""
#: pkg/build/script_executor.go:381
msgid "Executing build()"
msgstr ""
#: pkg/build/script_executor.go:410 pkg/build/script_executor.go:430
msgid "Executing %s()"
msgstr ""
#: pkg/repos/pull.go:77
msgid "Pulling repository"
msgstr ""
#: pkg/repos/pull.go:113
msgid "Repository up to date"
msgstr ""
#: pkg/repos/pull.go:204
msgid "Git repository does not appear to be a valid ALR repo"
msgstr ""
#: pkg/repos/pull.go:220
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
msgstr ""
#: refresh.go:30
msgid "Pull all repositories that have changed"
msgstr ""
@ -545,14 +545,14 @@ msgstr ""
msgid "Error while executing search"
msgstr ""
#: upgrade.go:47
#: upgrade.go:48
msgid "Upgrade all installed packages"
msgstr ""
#: upgrade.go:105 upgrade.go:122
#: upgrade.go:106 upgrade.go:123
msgid "Error checking for updates"
msgstr ""
#: upgrade.go:125
#: upgrade.go:126
msgid "There is nothing to do."
msgstr ""

View File

@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: unnamed project\n"
"PO-Revision-Date: 2025-05-16 20:47+0300\n"
"PO-Revision-Date: 2025-06-15 16:05+0300\n"
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
"Language-Team: Russian\n"
"Language: ru\n"
@ -65,11 +65,11 @@ msgstr "Ошибка при перемещении пакета"
msgid "Done"
msgstr "Сделано"
#: fix.go:38
#: fix.go:39
msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR"
#: fix.go:59
#: fix.go:60
msgid "Clearing cache directory"
msgstr "Очистка каталога кэша"
@ -81,15 +81,15 @@ msgstr "Невозможно открыть каталог кэша"
msgid "Unable to read cache directory contents"
msgstr "Невозможно прочитать содержимое каталога кэша"
#: fix.go:76
#: fix.go:82
msgid "Unable to remove cache item (%s)"
msgstr "Невозможно удалить элемент кэша (%s)"
#: fix.go:80
#: fix.go:86
msgid "Rebuilding cache"
msgstr "Восстановление кэша"
#: fix.go:84
#: fix.go:90
msgid "Unable to create new cache directory"
msgstr "Не удалось создать новый каталог кэша"
@ -133,58 +133,124 @@ msgstr "Показывать всю информацию, не только дл
msgid "Error getting packages"
msgstr "Ошибка при получении пакетов"
#: info.go:76
msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов"
#: info.go:90
#: info.go:83
msgid "Command info expected at least 1 argument, got %d"
msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d"
#: info.go:110
#: info.go:104
msgid "Error finding packages"
msgstr "Ошибка при поиске пакетов"
#: info.go:124
#: info.go:118
msgid "Can't detect system language"
msgstr "Ошибка при определении языка системы"
#: info.go:141
#: info.go:135
msgid "Error resolving overrides"
msgstr "Ошибка устранения переорпеделений"
#: info.go:149 info.go:154
#: info.go:144 info.go:149
msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита"
#: install.go:40
#: install.go:39
msgid "Install a new package"
msgstr "Установить новый пакет"
#: install.go:52
#: install.go:51
msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
#: install.go:114
#: install.go:113
msgid "Error when installing the package"
msgstr "Ошибка при установке пакета"
#: install.go:159
#: install.go:151
msgid "Remove an installed package"
msgstr "Удалить установленный пакет"
#: install.go:178
#: install.go:170
msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов"
#: install.go:215
#: install.go:199
msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
#: install.go:230
#: install.go:214
msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов"
#: internal/build/build.go:376
msgid "Building package"
msgstr "Сборка пакета"
#: internal/build/build.go:405
msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
#: internal/build/build.go:447
msgid "Downloading sources"
msgstr "Скачивание источников"
#: internal/build/build.go:539
msgid "Installing dependencies"
msgstr "Установка зависимостей"
#: internal/build/checker.go:43
msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?"
msgstr ""
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
"равно хотите выполнить сборку?"
#: internal/build/checker.go:67
msgid "This package is already installed"
msgstr "Этот пакет уже установлен"
#: internal/build/find_deps/alt_linux.go:35
msgid "Command not found on the system"
msgstr "Команда не найдена в системе"
#: internal/build/find_deps/alt_linux.go:86
msgid "Provided dependency found"
msgstr "Найденная предоставленная зависимость"
#: internal/build/find_deps/alt_linux.go:93
msgid "Required dependency found"
msgstr "Найдена требуемая зависимость"
#: internal/build/find_deps/empty.go:32
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
#: internal/build/find_deps/empty.go:37
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: internal/build/firejail.go:59
msgid "Applying FireJail integration"
msgstr "Применение интеграции FireJail"
#: internal/build/script_executor.go:145
msgid "Building package metadata"
msgstr "Сборка метаданных пакета"
#: internal/build/script_executor.go:285
msgid "Executing prepare()"
msgstr "Выполнение prepare()"
#: internal/build/script_executor.go:294
msgid "Executing build()"
msgstr "Выполнение build()"
#: internal/build/script_executor.go:323 internal/build/script_executor.go:343
msgid "Executing %s()"
msgstr "Выполнение %s()"
#: internal/cliutils/app_builder/builder.go:75
msgid "Error loading config"
msgstr "Ошибка при загрузке"
@ -193,15 +259,15 @@ msgstr "Ошибка при загрузке"
msgid "Error initialization database"
msgstr "Ошибка инициализации базы данных"
#: internal/cliutils/app_builder/builder.go:135
#: internal/cliutils/app_builder/builder.go:142
msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев"
#: internal/cliutils/app_builder/builder.go:152
#: internal/cliutils/app_builder/builder.go:159
msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: internal/cliutils/app_builder/builder.go:165
#: internal/cliutils/app_builder/builder.go:172
msgid "Unable to detect a supported package manager on the system"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
@ -293,11 +359,11 @@ msgstr ""
"Эта команда устарела и будет удалена в будущем, используйте вместо нее "
"\"%s\"!"
#: internal/db/db.go:137
#: internal/db/db.go:76
msgid "Database version mismatch; resetting"
msgstr "Несоответствие версий базы данных; сброс настроек"
#: internal/db/db.go:144
#: internal/db/db.go:82
msgid ""
"Database version does not exist. Run alr fix if something isn't working."
msgstr ""
@ -331,6 +397,26 @@ msgstr "%s %s загружается — %s/с\n"
msgid "ERROR"
msgstr "ОШИБКА"
#: internal/repos/pull.go:77
msgid "Pulling repository"
msgstr "Скачивание репозитория"
#: internal/repos/pull.go:113
msgid "Repository up to date"
msgstr "Репозиторий уже обновлён"
#: internal/repos/pull.go:204
msgid "Git repository does not appear to be a valid ALR repo"
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
#: internal/repos/pull.go:220
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает."
#: internal/utils/cmd.go:97
msgid "Error on dropping capabilities"
msgstr "Ошибка при понижении привилегий"
@ -359,11 +445,11 @@ msgstr "Ошибка при получении пакетов для обнов
msgid "No packages for upgrade"
msgstr "Нет пакетов к обновлению"
#: list.go:102 list.go:187
#: list.go:102 list.go:184
msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона"
#: list.go:108 list.go:191
#: list.go:108 list.go:188
msgid "Error executing template"
msgstr "Ошибка при выполнении шаблона"
@ -387,92 +473,6 @@ msgstr "Показать справку"
msgid "Error while running app"
msgstr "Ошибка при запуске приложения"
#: pkg/build/build.go:395
msgid "Building package"
msgstr "Сборка пакета"
#: pkg/build/build.go:424
msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
#: pkg/build/build.go:455
msgid "Downloading sources"
msgstr "Скачивание источников"
#: pkg/build/build.go:549
msgid "Installing dependencies"
msgstr "Установка зависимостей"
#: pkg/build/checker.go:43
msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?"
msgstr ""
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
"равно хотите выполнить сборку?"
#: pkg/build/checker.go:67
msgid "This package is already installed"
msgstr "Этот пакет уже установлен"
#: pkg/build/find_deps/alt_linux.go:35
msgid "Command not found on the system"
msgstr "Команда не найдена в системе"
#: pkg/build/find_deps/alt_linux.go:86
msgid "Provided dependency found"
msgstr "Найденная предоставленная зависимость"
#: pkg/build/find_deps/alt_linux.go:93
msgid "Required dependency found"
msgstr "Найдена требуемая зависимость"
#: pkg/build/find_deps/empty.go:32
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/find_deps/empty.go:37
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/script_executor.go:241
msgid "Building package metadata"
msgstr "Сборка метаданных пакета"
#: pkg/build/script_executor.go:372
msgid "Executing prepare()"
msgstr "Выполнение prepare()"
#: pkg/build/script_executor.go:381
msgid "Executing build()"
msgstr "Выполнение build()"
#: pkg/build/script_executor.go:410 pkg/build/script_executor.go:430
msgid "Executing %s()"
msgstr "Выполнение %s()"
#: pkg/repos/pull.go:77
msgid "Pulling repository"
msgstr "Скачивание репозитория"
#: pkg/repos/pull.go:113
msgid "Repository up to date"
msgstr "Репозиторий уже обновлён"
#: pkg/repos/pull.go:204
msgid "Git repository does not appear to be a valid ALR repo"
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
#: pkg/repos/pull.go:220
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает."
#: refresh.go:30
msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории"
@ -561,18 +561,25 @@ msgstr "Иcкать по provides"
msgid "Error while executing search"
msgstr "Ошибка при выполнении поиска"
#: upgrade.go:47
#: upgrade.go:48
msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты"
#: upgrade.go:105 upgrade.go:122
#: upgrade.go:106 upgrade.go:123
msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений"
#: upgrade.go:125
#: upgrade.go:126
msgid "There is nothing to do."
msgstr "Здесь нечего делать."
#, fuzzy
#~ msgid "Failed to clear contents of cache directory"
#~ msgstr "Не удалось создать каталог кэша репозитория"
#~ msgid "Error iterating over packages"
#~ msgstr "Ошибка при переборе пакетов"
#~ msgid "Error pulling repos"
#~ msgstr "Ошибка при извлечении репозиториев"
@ -588,9 +595,6 @@ msgstr "Здесь нечего делать."
#~ msgid "Unable to create config directory"
#~ msgstr "Не удалось создать каталог конфигурации ALR"
#~ msgid "Unable to create repo cache directory"
#~ msgstr "Не удалось создать каталог кэша репозитория"
#~ msgid "Unable to create package cache directory"
#~ msgstr "Не удалось создать каталог кэша пакетов"

15
list.go
View File

@ -29,12 +29,12 @@ import (
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
func ListCmd() *cli.Command {
@ -69,9 +69,9 @@ func ListCmd() *cli.Command {
WithConfig().
WithDB().
WithManager().
WithDistroInfo().
// autoPull only
WithRepos().
WithDistroInfo().
Build()
if err != nil {
return err
@ -125,7 +125,6 @@ func ListCmd() *cli.Command {
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
}
defer result.Close()
installedAlrPackages := map[string]string{}
if c.Bool("installed") {
@ -150,9 +149,7 @@ func ListCmd() *cli.Command {
}
}
for result.Next() {
var pkg database.Package
err := result.StructScan(&pkg)
for _, pkg := range result {
if err != nil {
return cli.Exit(err, 1)
}
@ -162,7 +159,7 @@ func ListCmd() *cli.Command {
}
type packageInfo struct {
Package *database.Package
Package *alrsh.Package
Version string
}

View File

@ -33,8 +33,8 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"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/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
)

View File

@ -52,9 +52,9 @@
./internal/translations/files/lure.en.toml
./internal/translations/files/lure.ru.toml
./internal/translations/translations.go
./internal/types/build.go
./internal/types/config.go
./internal/types/repo.go
./pkg/types/build.go
./pkg/types/config.go
./pkg/types/repo.go
./list.go
./main.go
./pkg/build/build.go

205
pkg/alrsh/alrsh.go Normal file
View File

@ -0,0 +1,205 @@
// 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 alrsh
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type ScriptFile struct {
file *syntax.File
path string
}
func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string {
env := os.Environ()
env = append(
env,
"DISTRO_NAME="+info.Name,
"DISTRO_PRETTY_NAME="+info.PrettyName,
"DISTRO_ID="+info.ID,
"DISTRO_VERSION_ID="+info.VersionID,
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
"ARCH="+cpu.Arch(),
"NCPU="+strconv.Itoa(runtime.NumCPU()),
)
if dirs.ScriptDir != "" {
env = append(env, "scriptdir="+dirs.ScriptDir)
}
if dirs.PkgDir != "" {
env = append(env, "pkgdir="+dirs.PkgDir)
}
if dirs.SrcDir != "" {
env = append(env, "srcdir="+dirs.SrcDir)
}
return env
}
func (s *ScriptFile) ParseBuildVars(ctx context.Context, info *distro.OSRelease, packages []string) (string, []*Package, error) {
runner, err := s.createRunner(info)
if err != nil {
return "", nil, err
}
if err := runScript(ctx, runner, s.file); err != nil {
return "", nil, err
}
dec := newDecoder(info, runner)
pkgNames, err := ParseNames(dec)
if err != nil {
return "", nil, err
}
if len(pkgNames.Names) == 0 {
return "", nil, errors.New("package name is missing")
}
targetPackages := packages
if len(targetPackages) == 0 {
targetPackages = pkgNames.Names
}
varsOfPackages, err := s.createPackagesForBuildVars(ctx, dec, pkgNames, targetPackages)
if err != nil {
return "", nil, err
}
baseName := pkgNames.BasePkgName
if len(pkgNames.Names) == 1 {
baseName = pkgNames.Names[0]
}
return baseName, varsOfPackages, nil
}
func (s *ScriptFile) createRunner(info *distro.OSRelease) (*interp.Runner, error) {
scriptDir := filepath.Dir(s.path)
env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir})
return interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.StdIO(os.Stdin, os.Stderr, os.Stderr),
interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)),
interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)),
interp.StatHandler(handlers.RestrictedStat(scriptDir)),
interp.OpenHandler(handlers.RestrictedOpen(scriptDir)),
interp.Dir(scriptDir),
)
}
func (s *ScriptFile) createPackagesForBuildVars(
ctx context.Context,
dec *decoder.Decoder,
pkgNames *PackageNames,
targetPackages []string,
) ([]*Package, error) {
var varsOfPackages []*Package
if len(pkgNames.Names) == 1 {
var pkg Package
pkg.Name = pkgNames.Names[0]
if err := dec.DecodeVars(&pkg); err != nil {
return nil, err
}
varsOfPackages = append(varsOfPackages, &pkg)
return varsOfPackages, nil
}
for _, pkgName := range targetPackages {
pkg, err := s.createPackageFromMeta(ctx, dec, pkgName, pkgNames.BasePkgName)
if err != nil {
return nil, err
}
varsOfPackages = append(varsOfPackages, pkg)
}
return varsOfPackages, nil
}
func (s *ScriptFile) createPackageFromMeta(
ctx context.Context,
dec *decoder.Decoder,
pkgName, basePkgName string,
) (*Package, error) {
funcName := fmt.Sprintf("meta_%s", pkgName)
meta, ok := dec.GetFuncWithSubshell(funcName)
if !ok {
return nil, fmt.Errorf("func %s is missing", funcName)
}
metaRunner, err := meta(ctx)
if err != nil {
return nil, err
}
metaDecoder := decoder.New(&distro.OSRelease{}, metaRunner)
var vars Package
if err := metaDecoder.DecodeVars(&vars); err != nil {
return nil, err
}
vars.Name = pkgName
vars.BasePkgName = basePkgName
return &vars, nil
}
func runScript(ctx context.Context, runner *interp.Runner, fl *syntax.File) error {
runner.Reset()
return runner.Run(ctx, fl)
}
func newDecoder(info *distro.OSRelease, runner *interp.Runner) *decoder.Decoder {
d := decoder.New(info, runner)
// d.Overrides = false
// d.LikeDistros = false
return d
}
func (a *ScriptFile) Path() string {
return a.path
}
func (a *ScriptFile) File() *syntax.File {
return a.file
}

61
pkg/alrsh/gob.go Normal file
View File

@ -0,0 +1,61 @@
// 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 alrsh
import (
"bytes"
"encoding/gob"
"mvdan.cc/sh/v3/syntax"
"mvdan.cc/sh/v3/syntax/typedjson"
)
func (s *ScriptFile) GobEncode() ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(s.path); err != nil {
return nil, err
}
var fileBuf bytes.Buffer
if err := typedjson.Encode(&fileBuf, s.file); err != nil {
return nil, err
}
fileData := fileBuf.Bytes()
if err := enc.Encode(fileData); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (s *ScriptFile) GobDecode(data []byte) error {
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
if err := dec.Decode(&s.path); err != nil {
return err
}
var fileData []byte
if err := dec.Decode(&fileData); err != nil {
return err
}
fileReader := bytes.NewReader(fileData)
file, err := typedjson.Decode(fileReader)
if err != nil {
return err
}
s.file = file.(*syntax.File)
return nil
}

146
pkg/alrsh/overridable.go Normal file
View File

@ -0,0 +1,146 @@
// 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 alrsh
import (
"bytes"
"encoding/gob"
"encoding/json"
)
type OverridableField[T any] struct {
data map[string]T
// It can't be a pointer
//
// See https://gitea.com/xorm/xorm/issues/2431
resolved T
}
func (f *OverridableField[T]) Set(key string, value T) {
if f.data == nil {
f.data = make(map[string]T)
}
f.data[key] = value
}
func (f *OverridableField[T]) Get(key string) T {
if f.data == nil {
f.data = make(map[string]T)
}
return f.data[key]
}
func (f *OverridableField[T]) Has(key string) (T, bool) {
if f.data == nil {
f.data = make(map[string]T)
}
val, ok := f.data[key]
return val, ok
}
func (f *OverridableField[T]) SetResolved(value T) {
f.resolved = value
}
func (f *OverridableField[T]) Resolved() T {
return f.resolved
}
func (f *OverridableField[T]) All() map[string]T {
return f.data
}
func (o *OverridableField[T]) Resolve(overrides []string) {
for _, override := range overrides {
if v, ok := o.Has(override); ok {
o.SetResolved(v)
}
}
}
func (f *OverridableField[T]) ToDB() ([]byte, error) {
var data map[string]T
if f.data == nil {
data = make(map[string]T)
} else {
data = f.data
}
return json.Marshal(data)
}
func (f *OverridableField[T]) FromDB(data []byte) error {
if len(data) == 0 {
*f = OverridableField[T]{data: make(map[string]T)}
return nil
}
var temp map[string]T
if err := json.Unmarshal(data, &temp); err != nil {
return err
}
if temp == nil {
temp = make(map[string]T)
}
*f = OverridableField[T]{data: temp}
return nil
}
type overridableFieldGobPayload[T any] struct {
Data map[string]T
Resolved T
}
func (f *OverridableField[T]) GobEncode() ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
payload := overridableFieldGobPayload[T]{
Data: f.data,
Resolved: f.resolved,
}
if err := enc.Encode(payload); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (f *OverridableField[T]) GobDecode(data []byte) error {
dec := gob.NewDecoder(bytes.NewBuffer(data))
var payload overridableFieldGobPayload[T]
if err := dec.Decode(&payload); err != nil {
return err
}
f.data = payload.Data
f.resolved = payload.Resolved
return nil
}
func OverridableFromMap[T any](data map[string]T) OverridableField[T] {
if data == nil {
data = make(map[string]T)
}
return OverridableField[T]{
data: data,
}
}

108
pkg/alrsh/package.go Normal file
View File

@ -0,0 +1,108 @@
// 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 alrsh
import (
"fmt"
"reflect"
"strings"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
)
type PackageNames struct {
BasePkgName string `sh:"basepkg_name"`
Names []string `sh:"name"`
}
func ParseNames(dec *decoder.Decoder) (*PackageNames, error) {
var pkgs PackageNames
err := dec.DecodeVars(&pkgs)
if err != nil {
return nil, fmt.Errorf("fail parse names: %w", err)
}
return &pkgs, nil
}
type Package struct {
Repository string `xorm:"pk 'repository'"`
Name string `xorm:"pk 'name'"`
BasePkgName string `xorm:"notnull 'basepkg_name'"`
Version string `sh:"version" xorm:"notnull 'version'"`
Release int `sh:"release" xorm:"notnull 'release'"`
Epoch uint `sh:"epoch" xorm:"'epoch'"`
Architectures []string `sh:"architectures" xorm:"json 'architectures'"`
Licenses []string `sh:"license" xorm:"json 'licenses'"`
Provides []string `sh:"provides" xorm:"json 'provides'"`
Conflicts []string `sh:"conflicts" xorm:"json 'conflicts'"`
Replaces []string `sh:"replaces" xorm:"json 'replaces'"`
Summary OverridableField[string] `sh:"summary" xorm:"'summary'"`
Description OverridableField[string] `sh:"desc" xorm:"'description'"`
Group OverridableField[string] `sh:"group" xorm:"'group_name'"`
Homepage OverridableField[string] `sh:"homepage" xorm:"'homepage'"`
Maintainer OverridableField[string] `sh:"maintainer" xorm:"'maintainer'"`
Depends OverridableField[[]string] `sh:"deps" xorm:"'depends'"`
BuildDepends OverridableField[[]string] `sh:"build_deps" xorm:"'builddepends'"`
OptDepends OverridableField[[]string] `sh:"opt_deps" xorm:"'optdepends'"`
Sources OverridableField[[]string] `sh:"sources" xorm:"-"`
Checksums OverridableField[[]string] `sh:"checksums" xorm:"-"`
Backup OverridableField[[]string] `sh:"backup" xorm:"-"`
Scripts OverridableField[Scripts] `sh:"scripts" xorm:"-"`
AutoReq OverridableField[[]string] `sh:"auto_req" xorm:"-"`
AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-"`
AutoReqSkipList OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-"`
AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-"`
FireJailed OverridableField[bool] `sh:"firejailed" xorm:"-"`
FireJailProfiles OverridableField[map[string]string] `sh:"firejail_profiles" xorm:"-"`
}
type Scripts struct {
PreInstall string `sh:"preinstall"`
PostInstall string `sh:"postinstall"`
PreRemove string `sh:"preremove"`
PostRemove string `sh:"postremove"`
PreUpgrade string `sh:"preupgrade"`
PostUpgrade string `sh:"postupgrade"`
PreTrans string `sh:"pretrans"`
PostTrans string `sh:"posttrans"`
}
func ResolvePackage(p *Package, overrides []string) {
val := reflect.ValueOf(p).Elem()
typ := val.Type()
for i := range val.NumField() {
field := val.Field(i)
fieldType := typ.Field(i)
if !field.CanInterface() {
continue
}
if field.Kind() == reflect.Struct && strings.HasPrefix(fieldType.Type.String(), "alrsh.OverridableField") {
of := field.Addr().Interface()
if res, ok := of.(interface {
Resolve([]string)
}); ok {
res.Resolve(overrides)
}
}
}
}

57
pkg/alrsh/read.go Normal file
View File

@ -0,0 +1,57 @@
// 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 alrsh
import (
"fmt"
"io"
"io/fs"
"os"
"mvdan.cc/sh/v3/syntax"
)
type localFs struct{}
func (fs *localFs) Open(name string) (fs.File, error) {
return os.Open(name)
}
func ReadFromIOReader(r io.Reader, script string) (*ScriptFile, error) {
file, err := syntax.NewParser().Parse(r, "alr.sh")
if err != nil {
return nil, err
}
return &ScriptFile{
file: file,
path: script,
}, nil
}
func ReadFromFS(fsys fs.FS, script string) (*ScriptFile, error) {
fl, err := fsys.Open(script)
if err != nil {
return nil, fmt.Errorf("failed to open alr.sh: %w", err)
}
defer fl.Close()
return ReadFromIOReader(fl, script)
}
func ReadFromLocal(script string) (*ScriptFile, error) {
return ReadFromFS(&localFs{}, script)
}

View File

@ -1,266 +0,0 @@
// 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 repos
import (
"context"
"errors"
"fmt"
"io"
"path/filepath"
"reflect"
"strings"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/format/diff"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/client"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/parser"
)
// isValid makes sure the path of the file being updated is valid.
// It checks to make sure the file is not within a nested directory
// and that it is called alr.sh.
func isValid(from, to diff.File) bool {
var path string
if from != nil {
path = from.Path()
}
if to != nil {
path = to.Path()
}
match, _ := filepath.Match("*/*.sh", path)
return match
}
func parseScript(
ctx context.Context,
repo types.Repo,
syntaxParser *syntax.Parser,
runner *interp.Runner,
r io.ReadCloser,
) ([]*db.Package, error) {
fl, err := syntaxParser.Parse(r, "alr.sh")
if err != nil {
return nil, err
}
runner.Reset()
err = runner.Run(ctx, fl)
if err != nil {
return nil, err
}
d := decoder.New(&distro.OSRelease{}, runner)
d.Overrides = false
d.LikeDistros = false
pkgNames, err := parser.ParseNames(d)
if err != nil {
return nil, fmt.Errorf("failed parsing package names: %w", err)
}
if len(pkgNames.Names) == 0 {
return nil, errors.New("package name is missing")
}
var dbPkgs []*db.Package
if len(pkgNames.Names) > 1 {
if pkgNames.BasePkgName == "" {
pkgNames.BasePkgName = pkgNames.Names[0]
}
for _, pkgName := range pkgNames.Names {
pkgInfo := PackageInfo{}
funcName := fmt.Sprintf("meta_%s", pkgName)
runner.Reset()
err = runner.Run(ctx, fl)
if err != nil {
return nil, err
}
meta, ok := d.GetFuncWithSubshell(funcName)
if !ok {
return nil, fmt.Errorf("func %s is missing", funcName)
}
r, err := meta(ctx)
if err != nil {
return nil, err
}
d := decoder.New(&distro.OSRelease{}, r)
d.Overrides = false
d.LikeDistros = false
err = d.DecodeVars(&pkgInfo)
if err != nil {
return nil, err
}
pkg := pkgInfo.ToPackage(repo.Name)
resolveOverrides(r, pkg)
pkg.Name = pkgName
pkg.BasePkgName = pkgNames.BasePkgName
dbPkgs = append(dbPkgs, pkg)
}
return dbPkgs, nil
}
pkg := EmptyPackage(repo.Name)
err = d.DecodeVars(pkg)
if err != nil {
return nil, err
}
resolveOverrides(runner, pkg)
dbPkgs = append(dbPkgs, pkg)
return dbPkgs, nil
}
type PackageInfo struct {
Version string `sh:"version,required"`
Release int `sh:"release,required"`
Epoch uint `sh:"epoch"`
Architectures db.JSON[[]string] `sh:"architectures"`
Licenses db.JSON[[]string] `sh:"license"`
Provides db.JSON[[]string] `sh:"provides"`
Conflicts db.JSON[[]string] `sh:"conflicts"`
Replaces db.JSON[[]string] `sh:"replaces"`
}
func (inf *PackageInfo) ToPackage(repoName string) *db.Package {
pkg := EmptyPackage(repoName)
pkg.Version = inf.Version
pkg.Release = inf.Release
pkg.Epoch = inf.Epoch
pkg.Architectures = inf.Architectures
pkg.Licenses = inf.Licenses
pkg.Provides = inf.Provides
pkg.Conflicts = inf.Conflicts
pkg.Replaces = inf.Replaces
return pkg
}
func EmptyPackage(repoName string) *db.Package {
return &db.Package{
Group: db.NewJSON(map[string]string{}),
Summary: db.NewJSON(map[string]string{}),
Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repoName,
}
}
var overridable = map[string]string{
"deps": "Depends",
"build_deps": "BuildDepends",
"desc": "Description",
"homepage": "Homepage",
"maintainer": "Maintainer",
"group": "Group",
"summary": "Summary",
}
func resolveOverrides(runner *interp.Runner, pkg *db.Package) {
pkgVal := reflect.ValueOf(pkg).Elem()
for name, val := range runner.Vars {
for prefix, field := range overridable {
if strings.HasPrefix(name, prefix) {
override := strings.TrimPrefix(name, prefix)
override = strings.TrimPrefix(override, "_")
field := pkgVal.FieldByName(field)
varVal := field.FieldByName("Val")
varType := varVal.Type()
switch varType.Elem().String() {
case "[]string":
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.List))
case "string":
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.Str))
}
break
}
}
}
}
func getHeadReference(r *git.Repository) (plumbing.ReferenceName, error) {
remote, err := r.Remote(git.DefaultRemoteName)
if err != nil {
return "", err
}
endpoint, err := transport.NewEndpoint(remote.Config().URLs[0])
if err != nil {
return "", err
}
gitClient, err := client.NewClient(endpoint)
if err != nil {
return "", err
}
session, err := gitClient.NewUploadPackSession(endpoint, nil)
if err != nil {
return "", err
}
info, err := session.AdvertisedReferences()
if err != nil {
return "", err
}
refs, err := info.AllReferences()
if err != nil {
return "", err
}
return refs["HEAD"].Target(), nil
}
func resolveHash(r *git.Repository, ref string) (*plumbing.Hash, error) {
var err error
if ref == "" {
reference, err := getHeadReference(r)
if err != nil {
return nil, fmt.Errorf("failed to get head reference %w", err)
}
ref = reference.Short()
}
hsh, err := r.ResolveRevision(git.DefaultRemoteName + "/" + plumbing.Revision(ref))
if err != nil {
hsh, err = r.ResolveRevision(plumbing.Revision(ref))
if err != nil {
return nil, err
}
}
return hsh, nil
}

View File

@ -24,49 +24,6 @@ type BuildOpts struct {
Interactive bool
}
type BuildVarsPre struct {
Version string `sh:"version,required"`
Release int `sh:"release,required"`
Epoch uint `sh:"epoch"`
Summary string `sh:"summary"`
Description string `sh:"desc"`
Group string `sh:"group"`
Homepage string `sh:"homepage"`
Maintainer string `sh:"maintainer"`
Architectures []string `sh:"architectures"`
Licenses []string `sh:"license"`
Provides []string `sh:"provides"`
Conflicts []string `sh:"conflicts"`
Depends []string `sh:"deps"`
BuildDepends []string `sh:"build_deps"`
OptDepends []string `sh:"opt_deps"`
Replaces []string `sh:"replaces"`
Sources []string `sh:"sources"`
Checksums []string `sh:"checksums"`
Backup []string `sh:"backup"`
Scripts Scripts `sh:"scripts"`
AutoReq []string `sh:"auto_req"`
AutoProv []string `sh:"auto_prov"`
AutoReqSkipList []string `sh:"auto_req_skiplist"`
AutoProvSkipList []string `sh:"auto_prov_skiplist"`
}
func (bv *BuildVarsPre) ToBuildVars() BuildVars {
return BuildVars{
Name: "",
Base: "",
BuildVarsPre: *bv,
}
}
// BuildVars represents the script variables required
// to build a package
type BuildVars struct {
Name string `sh:"name,required"`
Base string
BuildVarsPre
}
type Scripts struct {
PreInstall string `sh:"preinstall"`
PostInstall string `sh:"postinstall"`

66
pkg/types/script.go Normal file
View File

@ -0,0 +1,66 @@
// 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 types
import (
"bytes"
"encoding/gob"
"mvdan.cc/sh/v3/syntax"
"mvdan.cc/sh/v3/syntax/typedjson"
)
type ScriptFile struct {
File *syntax.File
Path string
}
func (s *ScriptFile) GobEncode() ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(s.Path); err != nil {
return nil, err
}
var fileBuf bytes.Buffer
if err := typedjson.Encode(&fileBuf, s.File); err != nil {
return nil, err
}
fileData := fileBuf.Bytes()
if err := enc.Encode(fileData); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (s *ScriptFile) GobDecode(data []byte) error {
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
if err := dec.Decode(&s.Path); err != nil {
return err
}
var fileData []byte
if err := dec.Decode(&fileData); err != nil {
return err
}
fileReader := bytes.NewReader(fileData)
file, err := typedjson.Decode(fileReader)
if err != nil {
return err
}
s.File = file.(*syntax.File)
return nil
}

View File

@ -29,8 +29,8 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
func RepoCmd() *cli.Command {

View File

@ -41,10 +41,10 @@ installPkg() {
fi
case $1 in
pacman) $rootCmd pacman --noconfirm -U ${@:2} ;;
apk) $rootCmd apk add --allow-untrusted ${@:2} ;;
zypper) $rootCmd zypper --no-gpg-checks install ${@:2} ;;
*) $rootCmd $1 install -y ${@:2} ;;
pacman) $rootCmd pacman --noconfirm -U "${@:2}" ;;
apk) $rootCmd apk add --allow-untrusted "${@:2}" ;;
zypper) $rootCmd zypper --no-gpg-checks install "${@:2}" ;;
*) $rootCmd "$1" install -y "${@:2}" ;;
esac
}
@ -88,60 +88,51 @@ else
fi
if [ -z "$noPkgMgr" ]; then
info "Получение списка файлов с https://plemya-x.ru/"
pageContent=$(curl -s https://plemya-x.ru/?dir=alr)
info "Получение списка файлов с https://gitea.plemya-x.ru/Plemya-x/ALR/releases"
# Изменено URL и регулярное выражение для списка файлов
pageContent=$(curl -s https://gitea.plemya-x.ru/Plemya-x/ALR/releases)
# Извлечение списка файлов из HTML
fileList=$(echo "$pageContent" | grep -oP '(?<=href=").*?(?=")' | grep -E 'alr-bin-.*.(pkg.tar.zst|rpm|deb)')
fileList=$(echo "$pageContent" | grep -oP '(?<=href=").*?(?=")' | grep -E 'alr-bin.*\.(pkg.tar.zst|rpm|deb)')
echo "Полученный список файлов:"
echo "$fileList"
if [ "$pkgMgr" == "pacman" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.pkg\.tar\.zst' | sort -V | tail -n 1)
elif [ "$pkgMgr" == "apt" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.amd64\.deb' | sort -V | tail -n 1)
elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.x86_64\.rpm' | grep -v 'alt1' | sort -V | tail -n 1)
elif [ "$pkgMgr" == "apt-get" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*-alt[0-9]+\.x86_64\.rpm' | sort -V | tail -n 1)
if [ "$pkgMgr" == "pacman" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.pkg\.tar\.zst' | sort -V | tail -n 1)
elif [ "$pkgMgr" == "apt" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.amd64\.deb' | sort -V | tail -n 1)
elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then
latestFile=$(printf "%s\n" "${fileList[@]}" | grep -E 'alr-bin-.*\.x86_64\.rpm' | grep -v 'alt[0-9]*' | sort -V | tail -n 1)
elif [ "$pkgMgr" == "apt-get" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*-alt[0-9]+\.x86_64\.rpm' | sort -V | tail -n 1)
else
error "Не поддерживаемый менеджер пакетов для автоматической установки"
fi
if [ -z "$latestFile" ]; then
error "Не удалось найти соответствующий пакет для $pkgMgr"
fi
info "Найдена последняя версия ALR: $latestFile"
fname="$(mktemp -u -p /tmp "alr.XXXXXXXXXX").${pkgFormat}"
info "Загрузка пакета ALR"
curl -o $fname -L "$latestFile"
if [ ! -f "$fname" ]; then
error "Ошибка загрузки пакета ALR"
fi
info "Установка пакета ALR"
installPkg "$pkgMgr" "$fname"
info "Очистка"
rm "$fname"
info "Готово!"
else
error "Не поддерживаемый менеджер пакетов для автоматической установки"
fi
if [ -z "$latestFile" ]; then
error "Не удалось найти соответствующий пакет для $pkgMgr"
fi
info "Найдена последняя версия ALR: $latestFile"
url="https://plemya-x.ru/$latestFile"
fname="$(mktemp -u -p /tmp "alr.XXXXXXXXXX").${pkgFormat}"
info "Загрузка пакета ALR"
curl -L $url -o $fname
if [ ! -f "$fname" ]; then
error "Ошибка загрузки пакета ALR"
fi
info "Установка пакета ALR"
installPkg $pkgMgr $fname
info "Очистка"
rm $fname
info "Готово!"
else
info "Клонирование репозитория ALR"
git clone https://gitea.plemya-x.ru/xpamych/ALR.git /tmp/alr
info "Установка ALR"
cd /tmp/alr
sudo make install
info "Очистка репозитория ALR"
rm -rf /tmp/alr
info "Все задачи выполнены!"
echo "Не найден поддерживаемый менеджер пакетов. О_о"
fi

View File

@ -27,11 +27,11 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/search"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"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/search"
)
func SearchCmd() *cli.Command {
@ -139,27 +139,16 @@ func SearchCmd() *cli.Command {
}
}
for _, dbPkg := range packages {
var pkg any
if !all {
pkg = overrides.ResolvePackage(&dbPkg, names)
} else {
pkg = &dbPkg
}
for _, pkg := range packages {
alrsh.ResolvePackage(&pkg, names)
if tmpl != nil {
err = tmpl.Execute(os.Stdout, pkg)
err = tmpl.Execute(os.Stdout, &pkg)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error executing template"), err)
}
fmt.Println()
} else {
switch v := pkg.(type) {
case *overrides.ResolvedPackage:
fmt.Println(v.Name)
case *db.Package:
fmt.Println(v.Name)
}
fmt.Println(pkg.Name)
}
}

View File

@ -29,16 +29,17 @@ import (
"go.elara.ws/vercmp"
"golang.org/x/exp/maps"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"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/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/search"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"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/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
func UpgradeCmd() *cli.Command {
@ -130,8 +131,8 @@ func UpgradeCmd() *cli.Command {
}
}
func mapUptatesInfoToPackages(updates []UpdateInfo) []database.Package {
var pkgs []database.Package
func mapUptatesInfoToPackages(updates []UpdateInfo) []alrsh.Package {
var pkgs []alrsh.Package
for _, info := range updates {
pkgs = append(pkgs, *info.Package)
}
@ -139,7 +140,7 @@ func mapUptatesInfoToPackages(updates []UpdateInfo) []database.Package {
}
type UpdateInfo struct {
Package *database.Package
Package *alrsh.Package
FromVersion string
ToVersion string