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

View File

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

View File

@ -45,9 +45,15 @@ jobs:
echo "Version - $version" echo "Version - $version"
echo "VERSION=$version" >> $GITHUB_ENV echo "VERSION=$version" >> $GITHUB_ENV
- name: Build alr binary - name: Prepare for install
run: | 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 - name: Create tar.gz
run: | run: |
@ -64,4 +70,51 @@ jobs:
body: ${{ steps.changes.outputs.changes }} body: ${{ steps.changes.outputs.changes }}
files: |- files: |-
alr-${{ env.VERSION }}-linux-x86_64.tar.gz 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 $@ go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
check-no-root: 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 "This target shouldn't run as root" 1>&2; \
echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \ echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \
exit 1; \ exit 1; \
@ -39,6 +39,12 @@ install: \
$(INSTALED_BIN): $(BIN) $(INSTALED_BIN): $(BIN)
install -Dm755 $< $@ install -Dm755 $< $@
setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN) 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) $(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION)
install -Dm755 $< $@ install -Dm755 $< $@

View File

@ -20,10 +20,10 @@ ALR написан на чистом Go и после сборки не имее
Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду: Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду:
```bash ```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. Репозитории 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"> <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="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">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="15" fill="#010101" fill-opacity=".3">19.0%</text>
<text x="86" y="14">16.9%</text> <text x="86" y="14">19.0%</text>
</g> </g>
</svg> </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/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" "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/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
) )
func BuildCmd() *cli.Command { func BuildCmd() *cli.Command {
@ -97,7 +97,7 @@ func BuildCmd() *cli.Command {
var script string var script string
var packages []string var packages []string
var res *build.BuildResult var res []*build.BuiltDep
var scriptArgs *build.BuildPackageFromScriptArgs var scriptArgs *build.BuildPackageFromScriptArgs
var dbArgs *build.BuildPackageFromDbArgs var dbArgs *build.BuildPackageFromDbArgs
@ -222,9 +222,9 @@ func BuildCmd() *cli.Command {
return cliutils.FormatCliExit(gotext.Get("Error building package"), err) return cliutils.FormatCliExit(gotext.Get("Error building package"), err)
} }
for _, pkgPath := range res.PackagePaths { for _, pkg := range res {
name := filepath.Base(pkgPath) name := filepath.Base(pkg.Path)
err = osutils.Move(pkgPath, filepath.Join(wd, name)) err = osutils.Move(pkg.Path, filepath.Join(wd, name))
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err) return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err)
} }

View File

@ -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_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) { func defaultPrepare(t *testing.T, r e2e.Runnable) {
execShouldNoError(t, r, 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, RPM_SYSTEMS,
func(t *testing.T, r e2e.Runnable) { func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r) 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 \"{{.Group.Resolved}}\" | 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 \"{{.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 package main
import ( import (
"io/fs"
"log/slog" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
@ -57,7 +58,6 @@ func FixCmd() *cli.Command {
paths := cfg.GetPaths() paths := cfg.GetPaths()
slog.Info(gotext.Get("Clearing cache directory")) slog.Info(gotext.Get("Clearing cache directory"))
// Remove all nested directories of paths.CacheDir
dir, err := os.Open(paths.CacheDir) dir, err := os.Open(paths.CacheDir)
if err != nil { if err != nil {
@ -71,7 +71,13 @@ func FixCmd() *cli.Command {
} }
for _, entry := range entries { 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 { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err) 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/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "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 { 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 gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PuerkitoBio/purell v1.2.0 github.com/PuerkitoBio/purell v1.2.0
github.com/alecthomas/assert/v2 v2.2.1
github.com/alecthomas/chroma/v2 v2.9.1 github.com/alecthomas/chroma/v2 v2.9.1
github.com/caarlos0/env v3.5.0+incompatible github.com/caarlos0/env v3.5.0+incompatible
github.com/charmbracelet/bubbles v0.20.0 github.com/charmbracelet/bubbles v0.20.0
@ -42,6 +41,7 @@ require (
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.25.0 modernc.org/sqlite v1.25.0
mvdan.cc/sh/v3 v3.10.0 mvdan.cc/sh/v3 v3.10.0
xorm.io/xorm v1.3.9
) )
require ( require (
@ -52,7 +52,6 @@ require (
github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // 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/andybalholm/brotli v1.0.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // 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-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/gobwas/glob v0.2.3 // 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/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // 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/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/yamux v0.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/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.16 // indirect github.com/imdario/mergo v0.3.16 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // 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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.11 // 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/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // 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/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.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/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // 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/skeema/knownhosts v1.3.0 // indirect
github.com/spf13/cast v1.6.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/therootcompany/xz v1.0.1 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect github.com/ulikunitz/xz v0.5.12 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
@ -145,4 +148,5 @@ require (
modernc.org/opt v0.1.3 // indirect modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // 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 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 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= 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 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw= gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= 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/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 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= 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= 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-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 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= 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.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 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 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/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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 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/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 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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= 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.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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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 h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= 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= 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/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 h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= 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 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 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= 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/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 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 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.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/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 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/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 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8=
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw= 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.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 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 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.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 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 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 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= 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/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 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 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 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= 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= 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/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 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 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.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/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 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= 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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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.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.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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.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 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns=
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ= 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= 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/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-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-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-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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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-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-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/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 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 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.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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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/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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 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" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
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/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "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/distro"
) )
@ -67,15 +67,8 @@ func InfoCmd() *cli.Command {
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) 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) fmt.Println(pkg.Name)
} }
return nil return nil
@ -96,6 +89,7 @@ func InfoCmd() *cli.Command {
New(ctx). New(ctx).
WithConfig(). WithConfig().
WithDB(). WithDB().
WithDistroInfo().
WithRepos(). WithRepos().
Build() Build()
if err != nil { if err != nil {
@ -144,7 +138,8 @@ func InfoCmd() *cli.Command {
for _, pkg := range pkgs { for _, pkg := range pkgs {
if !all { 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 { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
} }

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ import (
"github.com/goreleaser/nfpm/v2" "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 { type Cache struct {
@ -33,7 +33,7 @@ type Cache struct {
func (c *Cache) CheckForBuiltPackage( func (c *Cache) CheckForBuiltPackage(
ctx context.Context, ctx context.Context,
input *BuildInput, input *BuildInput,
vars *types.BuildVars, vars *alrsh.Package,
) (string, bool, error) { ) (string, bool, error) {
filename, err := pkgFileName(input, vars) filename, err := pkgFileName(input, vars)
if err != nil { if err != nil {
@ -56,7 +56,7 @@ func pkgFileName(
PkgFormatProvider PkgFormatProvider
RepositoryProvider RepositoryProvider
}, },
vars *types.BuildVars, vars *alrsh.Package,
) (string, error) { ) (string, error) {
pkgInfo := getBasePkgInfo(vars, input) 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/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" "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/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
) )
type Checker struct { type Checker struct {
@ -35,7 +35,7 @@ type Checker struct {
func (c *Checker) PerformChecks( func (c *Checker) PerformChecks(
ctx context.Context, ctx context.Context,
input *BuildInput, input *BuildInput,
vars *types.BuildVars, vars *alrsh.Package,
) (bool, error) { ) (bool, error) {
if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры
cont, err := cliutils.YesNoPrompt( cont, err := cliutils.YesNoPrompt(

View File

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

View File

@ -27,7 +27,7 @@ import (
"github.com/goreleaser/nfpm/v2" "github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext" "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 { 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/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext" "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{} type EmptyFindProvReq struct{}

View File

@ -28,7 +28,7 @@ import (
"github.com/goreleaser/nfpm/v2" "github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext" "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{} type FedoraFindProvReq struct{}

View File

@ -21,8 +21,8 @@ import (
"github.com/goreleaser/nfpm/v2" "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/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
) )
type ProvReqFinder interface { 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 package build
import ( 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 { func NewInstaller(mgr manager.Manager) *Installer {

View File

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

View File

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

View File

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

View File

@ -19,7 +19,6 @@ package build
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
@ -36,12 +35,12 @@ import (
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax" "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/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" "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/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
finddeps "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build/find_deps" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
) )
type LocalScriptExecutor struct { type LocalScriptExecutor struct {
@ -54,107 +53,12 @@ func NewLocalScriptExecutor(cfg Config) *LocalScriptExecutor {
} }
} }
func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) { func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
fl, err := readScript(scriptPath) return alrsh.ReadFromLocal(scriptPath)
if err != nil {
return nil, err
}
return &ScriptFile{
Path: scriptPath,
File: fl,
}, nil
} }
func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) { func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
varsOfPackages := []*types.BuildVars{} return sf.ParseBuildVars(ctx, input.info, input.packages)
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) PrepareDirs( func (e *LocalScriptExecutor) PrepareDirs(
@ -182,13 +86,13 @@ func (e *LocalScriptExecutor) PrepareDirs(
func (e *LocalScriptExecutor) ExecuteSecondPass( func (e *LocalScriptExecutor) ExecuteSecondPass(
ctx context.Context, ctx context.Context,
input *BuildInput, input *BuildInput,
sf *ScriptFile, sf *alrsh.ScriptFile,
varsOfPackages []*types.BuildVars, varsOfPackages []*alrsh.Package,
repoDeps []string, repoDeps []string,
builtNames []string, builtDeps []*BuiltDep,
basePkg string, basePkg string,
) (*SecondPassResult, error) { ) ([]*BuiltDep, error) {
dirs, err := getDirs(e.cfg, sf.Path, basePkg) dirs, err := getDirs(e.cfg, sf.Path(), basePkg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -206,14 +110,14 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
return nil, err return nil, err
} }
err = runner.Run(ctx, sf.File) err = runner.Run(ctx, sf.File())
if err != nil { if err != nil {
return nil, err return nil, err
} }
dec := decoder.New(input.info, runner) dec := decoder.New(input.info, runner)
var builtPaths []string // var builtPaths []string
err = e.ExecuteFunctions(ctx, dirs, dec) err = e.ExecuteFunctions(ctx, dirs, dec)
if err != nil { if err != nil {
@ -222,7 +126,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
for _, vars := range varsOfPackages { for _, vars := range varsOfPackages {
packageName := "" packageName := ""
if vars.Base != "" { if vars.BasePkgName != "" {
packageName = vars.Name packageName = vars.Name
} }
@ -247,7 +151,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
dirs, dirs,
append( append(
repoDeps, repoDeps,
builtNames..., GetBuiltName(builtDeps)...,
), ),
funcOut.Contents, funcOut.Contents,
) )
@ -273,14 +177,13 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
return nil, err return nil, err
} }
builtPaths = append(builtPaths, pkgPath) builtDeps = append(builtDeps, &BuiltDep{
builtNames = append(builtNames, vars.Name) Name: vars.Name,
Path: pkgPath,
})
} }
return &SecondPassResult{ return builtDeps, nil
BuiltPaths: builtPaths,
BuiltNames: builtNames,
}, nil
} }
func buildPkgMetadata( func buildPkgMetadata(
@ -291,24 +194,24 @@ func buildPkgMetadata(
PkgFormatProvider PkgFormatProvider
RepositoryProvider RepositoryProvider
}, },
vars *types.BuildVars, vars *alrsh.Package,
dirs types.Directories, dirs types.Directories,
deps []string, deps []string,
preferedContents *[]string, preferedContents *[]string,
) (*nfpm.Info, error) { ) (*nfpm.Info, error) {
pkgInfo := getBasePkgInfo(vars, input) pkgInfo := getBasePkgInfo(vars, input)
pkgInfo.Description = vars.Description pkgInfo.Description = vars.Description.Resolved()
pkgInfo.Platform = "linux" pkgInfo.Platform = "linux"
pkgInfo.Homepage = vars.Homepage pkgInfo.Homepage = vars.Homepage.Resolved()
pkgInfo.License = strings.Join(vars.Licenses, ", ") pkgInfo.License = strings.Join(vars.Licenses, ", ")
pkgInfo.Maintainer = vars.Maintainer pkgInfo.Maintainer = vars.Maintainer.Resolved()
pkgInfo.Overridables = nfpm.Overridables{ pkgInfo.Overridables = nfpm.Overridables{
Conflicts: append(vars.Conflicts, vars.Name), Conflicts: append(vars.Conflicts, vars.Name),
Replaces: vars.Replaces, Replaces: vars.Replaces,
Provides: append(vars.Provides, vars.Name), Provides: append(vars.Provides, vars.Name),
Depends: deps, Depends: deps,
} }
pkgInfo.Section = vars.Group pkgInfo.Section = vars.Group.Resolved()
pkgFormat := input.PkgFormat() pkgFormat := input.PkgFormat()
info := input.OSRelease() info := input.OSRelease()
@ -321,12 +224,12 @@ func buildPkgMetadata(
} }
if pkgFormat == "rpm" { if pkgFormat == "rpm" {
pkgInfo.RPM.Group = vars.Group pkgInfo.RPM.Group = vars.Group.Resolved()
if vars.Summary != "" { if vars.Summary.Resolved() != "" {
pkgInfo.RPM.Summary = vars.Summary pkgInfo.RPM.Summary = vars.Summary.Resolved()
} else { } else {
lines := strings.SplitN(vars.Description, "\n", 2) lines := strings.SplitN(vars.Description.Resolved(), "\n", 2)
pkgInfo.RPM.Summary = lines[0] pkgInfo.RPM.Summary = lines[0]
} }
} }
@ -345,19 +248,29 @@ func buildPkgMetadata(
if err != nil { if err != nil {
return nil, err return nil, err
} }
pkgInfo.Overridables.Contents = contents
if len(vars.AutoProv) == 1 && decoder.IsTruthy(vars.AutoProv[0]) { normalizeContents(contents)
f := finddeps.New(info, pkgFormat)
err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList) if vars.FireJailed.Resolved() {
contents, err = applyFirejailIntegration(vars, dirs, contents)
if err != nil { if err != nil {
return nil, err 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) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -18,9 +18,10 @@ package build
import ( import (
"context" "context"
"os"
"path/filepath" "path/filepath"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
) )
type ScriptResolver struct { type ScriptResolver struct {
@ -34,16 +35,27 @@ type ScriptInfo struct {
func (s *ScriptResolver) ResolveScript( func (s *ScriptResolver) ResolveScript(
ctx context.Context, ctx context.Context,
pkg *db.Package, pkg *alrsh.Package,
) *ScriptInfo { ) *ScriptInfo {
var repository, script string var repository, script string
repodir := s.cfg.GetPaths().RepoDir repodir := s.cfg.GetPaths().RepoDir
repository = pkg.Repository 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 { } 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{ return &ScriptInfo{

View File

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

View File

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

View File

@ -28,8 +28,8 @@ import (
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/leonelquinteros/gotext" "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/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 // 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 // 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. // 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 { func FlattenPkgs(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool) []alrsh.Package {
var outPkgs []db.Package var outPkgs []alrsh.Package
for _, pkgs := range found { for _, pkgs := range found {
if len(pkgs) > 1 && interactive { if len(pkgs) > 1 && interactive {
choice, err := PkgPrompt(ctx, pkgs, verb, 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. // 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 { if !interactive {
return options[0], nil return options[0], nil
} }
@ -138,7 +138,7 @@ func PkgPrompt(ctx context.Context, options []db.Package, verb string, interacti
var choice int var choice int
err := survey.AskOne(prompt, &choice) err := survey.AskOne(prompt, &choice)
if err != nil { if err != nil {
return db.Package{}, err return alrsh.Package{}, err
} }
return options[choice], nil return options[choice], nil

View File

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

View File

@ -23,41 +23,18 @@ import (
"context" "context"
"log/slog" "log/slog"
"github.com/jmoiron/sqlx"
"github.com/leonelquinteros/gotext" "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/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
) )
// CurrentVersion is the current version of the database. const CurrentVersion = 5
// The database is reset if its version doesn't match this.
const CurrentVersion = 4
// Package is a ALR package's database representation type Version struct {
type Package struct { Version int `xorm:"'version'"`
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 Config interface { type Config interface {
@ -65,7 +42,7 @@ type Config interface {
} }
type Database struct { type Database struct {
conn *sqlx.DB engine *xorm.Engine
config Config config Config
} }
@ -75,181 +52,100 @@ func New(config Config) *Database {
} }
} }
func (d *Database) Init(ctx context.Context) error { func (d *Database) Connect() error {
err := d.Connect(ctx)
if err != nil {
return err
}
return d.initDB(ctx)
}
func (d *Database) Connect(ctx context.Context) error {
dsn := d.config.GetPaths().DBPath 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 { if err != nil {
return err return err
} }
d.conn = db d.engine = engine
return nil return nil
} }
func (d *Database) GetConn() *sqlx.DB { func (d *Database) Init(ctx context.Context) error {
return d.conn if err := d.Connect(); err != nil {
} return err
}
func (d *Database) initDB(ctx context.Context) error { if err := d.engine.Sync2(new(alrsh.Package), new(Version)); err != nil {
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 {
return err return err
} }
ver, ok := d.GetVersion(ctx) ver, ok := d.GetVersion(ctx)
if ok && ver != CurrentVersion { if ok && ver != CurrentVersion {
slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion) slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion)
err = d.reset(ctx) if err := d.reset(); err != nil {
if err != nil {
return err return err
} }
return d.initDB(ctx) return d.Init(ctx)
} else if !ok { } else if !ok {
slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion) slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."))
return d.addVersion(ctx, CurrentVersion) return d.addVersion(CurrentVersion)
} }
return nil return nil
} }
func (d *Database) GetVersion(ctx context.Context) (int, bool) { func (d *Database) GetVersion(ctx context.Context) (int, bool) {
var ver version var v Version
err := d.conn.GetContext(ctx, &ver, "SELECT * FROM alr_db_version LIMIT 1;") has, err := d.engine.Get(&v)
if err != nil { if err != nil || !has {
return 0, false return 0, false
} }
return ver.Version, true return v.Version, true
} }
func (d *Database) addVersion(ctx context.Context, ver int) error { func (d *Database) addVersion(ver int) error {
_, err := d.conn.ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver) _, err := d.engine.Insert(&Version{Version: ver})
return err return err
} }
func (d *Database) reset(ctx context.Context) error { func (d *Database) reset() error {
_, err := d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;") 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 { if err != nil {
return err 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) { func (d *Database) GetPkgs(_ context.Context, where string, args ...any) ([]alrsh.Package, error) {
stream, err := d.conn.QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...) var pkgs []alrsh.Package
if err != nil { 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 nil, err
} }
return stream, nil return &pkg, nil
} }
func (d *Database) GetPkg(ctx context.Context, where string, args ...any) (*Package, error) { func (d *Database) DeletePkgs(_ context.Context, where string, args ...any) error {
out := &Package{} _, err := d.engine.Where(where, args...).Delete(&alrsh.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...)
return err return err
} }
func (d *Database) IsEmpty(ctx context.Context) bool { func (d *Database) IsEmpty() bool {
var count int count, err := d.engine.Count(new(alrsh.Package))
err := d.conn.GetContext(ctx, &count, "SELECT count(1) FROM pkgs;") return err != nil || count == 0
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) Close() error { func (d *Database) Close() error {
if d.conn != nil { return d.engine.Close()
return d.conn.Close()
} else {
return nil
}
} }

View File

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

View File

@ -21,7 +21,6 @@ package overrides
import ( import (
"fmt" "fmt"
"reflect"
"regexp" "regexp"
"strings" "strings"
@ -29,7 +28,6 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" "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" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
) )
@ -150,65 +148,6 @@ func (o *Opts) WithLanguageTags(langs []string) *Opts {
return out 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) { func parseLangs(langs []string, tags []language.Tag) ([]string, error) {
out := make([]string, len(tags)+len(langs)) out := make([]string, len(tags)+len(langs))
for i, tag := range tags { for i, tag := range tags {

View File

@ -22,11 +22,11 @@ package repos
import ( import (
"context" "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) { func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error) {
found := map[string][]db.Package{} found := map[string][]alrsh.Package{}
notFound := []string(nil) notFound := []string(nil)
for _, pkgName := range pkgs { for _, pkgName := range pkgs {
@ -40,17 +40,10 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.P
} }
added := 0 added := 0
for result.Next() { for _, pkg := range result {
var pkg db.Package
err = result.StructScan(&pkg)
if err != nil {
return nil, nil, err
}
added++ added++
found[pkgName] = append(found[pkgName], pkg) found[pkgName] = append(found[pkgName], pkg)
} }
result.Close()
if added == 0 { if added == 0 {
result, err := rs.db.GetPkgs(ctx, "name LIKE ?", pkgName) 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 return nil, nil, err
} }
for result.Next() { for _, pkg := range result {
var pkg db.Package
err = result.StructScan(&pkg)
if err != nil {
return nil, nil, err
}
added++ added++
found[pkgName] = append(found[pkgName], pkg) found[pkgName] = append(found[pkgName], pkg)
} }
result.Close()
} }
if added == 0 { if added == 0 {

View File

@ -24,9 +24,9 @@ import (
"strings" "strings"
"testing" "testing"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
"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/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
) )
func TestFindPkgs(t *testing.T) { func TestFindPkgs(t *testing.T) {
@ -41,7 +41,7 @@ func TestFindPkgs(t *testing.T) {
err := rs.Pull(e.Ctx, []types.Repo{ err := rs.Pull(e.Ctx, []types.Repo{
{ {
Name: "default", 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 { if err != nil {
@ -89,31 +89,31 @@ func TestFindPkgsEmpty(t *testing.T) {
e.Db, e.Db,
) )
err := e.Db.InsertPackage(e.Ctx, db.Package{ err := e.Db.InsertPackage(e.Ctx, alrsh.Package{
Name: "test1", Name: "test1",
Repository: "default", Repository: "default",
Version: "0.0.1", Version: "0.0.1",
Release: 1, Release: 1,
Description: db.NewJSON(map[string]string{ Provides: []string{""},
Description: alrsh.OverridableFromMap(map[string]string{
"en": "Test package 1", "en": "Test package 1",
"ru": "Проверочный пакет 1", "ru": "Проверочный пакет 1",
}), }),
Provides: db.NewJSON([]string{""}),
}) })
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) 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", Name: "test2",
Repository: "default", Repository: "default",
Version: "0.0.1", Version: "0.0.1",
Release: 1, Release: 1,
Description: db.NewJSON(map[string]string{ Provides: []string{"test"},
Description: alrsh.OverridableFromMap(map[string]string{
"en": "Test package 2", "en": "Test package 2",
"ru": "Проверочный пакет 2", "ru": "Проверочный пакет 2",
}), }),
Provides: db.NewJSON([]string{"test"}),
}) })
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) 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/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" "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 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 // If the DB was not present at startup, that means it's
// empty. In this case, we need to update the DB fully // empty. In this case, we need to update the DB fully
// rather than just incrementally. // rather than just incrementally.
if rs.db.IsEmpty(ctx) { if rs.db.IsEmpty() {
err = rs.processRepoFull(ctx, repo, repoDir) err = rs.processRepoFull(ctx, repo, repoDir)
if err != nil { if err != nil {
return err return err
@ -277,7 +277,15 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
for _, fp := range patch.FilePatches() { for _, fp := range patch.FilePatches() {
from, to := fp.Files() 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 continue
} }
@ -316,55 +324,60 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
parser := syntax.NewParser() parser := syntax.NewParser()
for _, action := range actions { 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 { if err != nil {
return fmt.Errorf("error creating process repo changes runner: %w", err) return fmt.Errorf("error creating process repo changes runner: %w", err)
} }
switch action.Type { switch action.Type {
case actionDelete: case actionDelete:
if filepath.Base(action.File) != "alr.sh" {
continue
}
scriptFl, err := oldCommit.File(action.File) scriptFl, err := oldCommit.File(action.File)
if err != nil { 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() r, err := scriptFl.Reader()
if err != nil { 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) pkgs, err := parseScript(ctx, repo, parser, runner, r)
if err != nil { if err != nil {
return err return fmt.Errorf("error parsing deleted script %s: %w", action.File, err)
} }
for _, pkg := range pkgs { for _, pkg := range pkgs {
err = rs.db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name) err = rs.db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name)
if err != nil { 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) scriptFl, err := newCommit.File(action.File)
if err != nil { 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() r, err := scriptFl.Reader()
if err != nil { 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) err = rs.updatePkg(ctx, repo, runner, r)
if err != nil { 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 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 { 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) matches, err := filepath.Glob(glob)
if err != nil { 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 { for _, match := range matches {
runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match)) runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match))
if err != nil { if err != nil {
return err return fmt.Errorf("error creating runner for %s: %w", match, err)
} }
scriptFl, err := os.Open(match) scriptFl, err := os.Open(match)
if err != nil { if err != nil {
return err return fmt.Errorf("error opening %s: %w", match, err)
} }
err = rs.updatePkg(ctx, repo, runner, scriptFl) err = rs.updatePkg(ctx, repo, runner, scriptFl)
scriptFl.Close()
if err != nil { 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/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "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{} type TestALRConfig struct{}
@ -84,16 +85,10 @@ build_deps=('golang')
result, err := database.GetPkgs(ctx, "1 = 1") result, err := database.GetPkgs(ctx, "1 = 1")
assert.NoError(t, err) assert.NoError(t, err)
pkgCount := 0 pkgCount := 0
for result.Next() { for _, pkg := range result {
var dbPkg db.Package assert.Equal(t, "foo", pkg.Name)
err = result.StructScan(&dbPkg) assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "main desc"}), pkg.Description)
if err != nil { assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo"}}), pkg.Depends)
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)
pkgCount++ pkgCount++
} }
assert.Equal(t, 1, pkgCount) assert.Equal(t, 1, pkgCount)
@ -125,20 +120,18 @@ meta_buz() {
assert.NoError(t, err) assert.NoError(t, err)
pkgCount := 0 pkgCount := 0
for result.Next() { for _, pkg := range result {
var dbPkg db.Package
err = result.StructScan(&dbPkg)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
if dbPkg.Name == "bar" { if pkg.Name == "bar" {
assert.Equal(t, db.NewJSON(map[string]string{"": "foo desc"}), dbPkg.Description) assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "foo desc"}), pkg.Description)
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends) assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo"}}), pkg.Depends)
} }
if dbPkg.Name == "buz" { if pkg.Name == "buz" {
assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description) assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "main desc"}), pkg.Description)
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo", "doas"}}), dbPkg.Depends) assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo", "doas"}}), pkg.Depends)
} }
pkgCount++ 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/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
database "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/internal/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
) )
type TestEnv struct { type TestEnv struct {
@ -129,15 +129,7 @@ func TestPull(t *testing.T) {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
var pkgAmt int pkgAmt := len(result)
for result.Next() {
var dbPkg db.Package
err = result.StructScan(&dbPkg)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
pkgAmt++
}
if pkgAmt == 0 { if pkgAmt == 0 {
t.Errorf("Expected at least 1 matching package, but got %d", pkgAmt) t.Errorf("Expected at least 1 matching package, but got %d", pkgAmt)

View File

@ -19,7 +19,7 @@ package repos
import ( import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "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/types"
) )
type Config interface { 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 ( import (
"context" "context"
"github.com/jmoiron/sqlx" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
) )
type PackagesProvider interface { 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 { type Searcher struct {
@ -44,23 +42,8 @@ func New(pp PackagesProvider) *Searcher {
func (s *Searcher) Search( func (s *Searcher) Search(
ctx context.Context, ctx context.Context,
opts *SearchOptions, opts *SearchOptions,
) ([]database.Package, error) { ) ([]alrsh.Package, error) {
var packages []database.Package
where, args := opts.WhereClause() where, args := opts.WhereClause()
result, err := s.pp.GetPkgs(ctx, where, args...) packages, err := s.pp.GetPkgs(ctx, where, args...)
if err != nil { return packages, err
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
} }

View File

@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert" "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) { func TestSearhOptionsBuilder(t *testing.T) {

View File

@ -22,6 +22,8 @@ package decoder
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"log/slog"
"reflect" "reflect"
"strings" "strings"
@ -52,7 +54,7 @@ type InvalidTypeError struct {
} }
func (ite InvalidTypeError) Error() string { 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 // 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{ dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
WeaklyTypedInput: true, WeaklyTypedInput: true,
DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) { DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) {
if strings.Contains(to.Type().String(), "db.JSON") { if strings.Contains(to.Type().String(), "alrsh.OverridableField") {
valType := to.FieldByName("Val").Type() if to.Kind() != reflect.Ptr && to.CanAddr() {
if !from.Type().AssignableTo(valType) { to = to.Addr()
return nil, InvalidTypeError{name, from.Type().String(), valType.String()} }
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 to, nil
} }
return from.Interface(), nil return from.Interface(), nil
@ -96,6 +133,7 @@ func (d *Decoder) DecodeVar(name string, val any) error {
TagName: "sh", TagName: "sh",
}) })
if err != nil { if err != nil {
slog.Warn("err", "err", err)
return err return err
} }
@ -255,23 +293,31 @@ func (d *Decoder) getVar(name string) *expand.Variable {
} }
for _, varName := range names { for _, varName := range names {
val, ok := d.Runner.Vars[varName] res := d.getVarNoOverrides(varName)
if ok { if res != nil {
// Resolve nameref variables return res
_, 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 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 { func IsTruthy(value string) bool {
value = strings.ToLower(strings.TrimSpace(value)) value = strings.ToLower(strings.TrimSpace(value))
return value == "true" || value == "yes" || value == "1" return value == "true" || value == "yes" || value == "1"

View File

@ -58,11 +58,11 @@ msgstr ""
msgid "Done" msgid "Done"
msgstr "" msgstr ""
#: fix.go:38 #: fix.go:39
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "" msgstr ""
#: fix.go:59 #: fix.go:60
msgid "Clearing cache directory" msgid "Clearing cache directory"
msgstr "" msgstr ""
@ -74,15 +74,15 @@ msgstr ""
msgid "Unable to read cache directory contents" msgid "Unable to read cache directory contents"
msgstr "" msgstr ""
#: fix.go:76 #: fix.go:82
msgid "Unable to remove cache item (%s)" msgid "Unable to remove cache item (%s)"
msgstr "" msgstr ""
#: fix.go:80 #: fix.go:86
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "" msgstr ""
#: fix.go:84 #: fix.go:90
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "" msgstr ""
@ -126,58 +126,120 @@ msgstr ""
msgid "Error getting packages" msgid "Error getting packages"
msgstr "" msgstr ""
#: info.go:76 #: info.go:83
msgid "Error iterating over packages"
msgstr ""
#: info.go:90
msgid "Command info expected at least 1 argument, got %d" msgid "Command info expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: info.go:110 #: info.go:104
msgid "Error finding packages" msgid "Error finding packages"
msgstr "" msgstr ""
#: info.go:124 #: info.go:118
msgid "Can't detect system language" msgid "Can't detect system language"
msgstr "" msgstr ""
#: info.go:141 #: info.go:135
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "" msgstr ""
#: info.go:149 info.go:154 #: info.go:144 info.go:149
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "" msgstr ""
#: install.go:40 #: install.go:39
msgid "Install a new package" msgid "Install a new package"
msgstr "" msgstr ""
#: install.go:52 #: install.go:51
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:114 #: install.go:113
msgid "Error when installing the package" msgid "Error when installing the package"
msgstr "" msgstr ""
#: install.go:159 #: install.go:151
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "" msgstr ""
#: install.go:178 #: install.go:170
msgid "Error listing installed packages" msgid "Error listing installed packages"
msgstr "" msgstr ""
#: install.go:215 #: install.go:199
msgid "Command remove expected at least 1 argument, got %d" msgid "Command remove expected at least 1 argument, got %d"
msgstr "" msgstr ""
#: install.go:230 #: install.go:214
msgid "Error removing packages" msgid "Error removing packages"
msgstr "" 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 #: internal/cliutils/app_builder/builder.go:75
msgid "Error loading config" msgid "Error loading config"
msgstr "" msgstr ""
@ -186,15 +248,15 @@ msgstr ""
msgid "Error initialization database" msgid "Error initialization database"
msgstr "" msgstr ""
#: internal/cliutils/app_builder/builder.go:135 #: internal/cliutils/app_builder/builder.go:142
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "" msgstr ""
#: internal/cliutils/app_builder/builder.go:152 #: internal/cliutils/app_builder/builder.go:159
msgid "Error parsing os release" msgid "Error parsing os release"
msgstr "" 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" msgid "Unable to detect a supported package manager on the system"
msgstr "" msgstr ""
@ -284,11 +346,11 @@ msgid ""
"instead!" "instead!"
msgstr "" msgstr ""
#: internal/db/db.go:137 #: internal/db/db.go:76
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "" msgstr ""
#: internal/db/db.go:144 #: internal/db/db.go:82
msgid "" msgid ""
"Database version does not exist. Run alr fix if something isn't working." "Database version does not exist. Run alr fix if something isn't working."
msgstr "" msgstr ""
@ -321,6 +383,24 @@ msgstr ""
msgid "ERROR" msgid "ERROR"
msgstr "" 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 #: internal/utils/cmd.go:97
msgid "Error on dropping capabilities" msgid "Error on dropping capabilities"
msgstr "" msgstr ""
@ -349,11 +429,11 @@ msgstr ""
msgid "No packages for upgrade" msgid "No packages for upgrade"
msgstr "" msgstr ""
#: list.go:102 list.go:187 #: list.go:102 list.go:184
msgid "Error parsing format template" msgid "Error parsing format template"
msgstr "" msgstr ""
#: list.go:108 list.go:191 #: list.go:108 list.go:188
msgid "Error executing template" msgid "Error executing template"
msgstr "" msgstr ""
@ -377,86 +457,6 @@ msgstr ""
msgid "Error while running app" msgid "Error while running app"
msgstr "" 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 #: refresh.go:30
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "" msgstr ""
@ -545,14 +545,14 @@ msgstr ""
msgid "Error while executing search" msgid "Error while executing search"
msgstr "" msgstr ""
#: upgrade.go:47 #: upgrade.go:48
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "" msgstr ""
#: upgrade.go:105 upgrade.go:122 #: upgrade.go:106 upgrade.go:123
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "" msgstr ""
#: upgrade.go:125 #: upgrade.go:126
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "" msgstr ""

View File

@ -5,7 +5,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: unnamed project\n" "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" "Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
"Language-Team: Russian\n" "Language-Team: Russian\n"
"Language: ru\n" "Language: ru\n"
@ -65,11 +65,11 @@ msgstr "Ошибка при перемещении пакета"
msgid "Done" msgid "Done"
msgstr "Сделано" msgstr "Сделано"
#: fix.go:38 #: fix.go:39
msgid "Attempt to fix problems with ALR" msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR" msgstr "Попытка устранить проблемы с ALR"
#: fix.go:59 #: fix.go:60
msgid "Clearing cache directory" msgid "Clearing cache directory"
msgstr "Очистка каталога кэша" msgstr "Очистка каталога кэша"
@ -81,15 +81,15 @@ msgstr "Невозможно открыть каталог кэша"
msgid "Unable to read cache directory contents" msgid "Unable to read cache directory contents"
msgstr "Невозможно прочитать содержимое каталога кэша" msgstr "Невозможно прочитать содержимое каталога кэша"
#: fix.go:76 #: fix.go:82
msgid "Unable to remove cache item (%s)" msgid "Unable to remove cache item (%s)"
msgstr "Невозможно удалить элемент кэша (%s)" msgstr "Невозможно удалить элемент кэша (%s)"
#: fix.go:80 #: fix.go:86
msgid "Rebuilding cache" msgid "Rebuilding cache"
msgstr "Восстановление кэша" msgstr "Восстановление кэша"
#: fix.go:84 #: fix.go:90
msgid "Unable to create new cache directory" msgid "Unable to create new cache directory"
msgstr "Не удалось создать новый каталог кэша" msgstr "Не удалось создать новый каталог кэша"
@ -133,58 +133,124 @@ msgstr "Показывать всю информацию, не только дл
msgid "Error getting packages" msgid "Error getting packages"
msgstr "Ошибка при получении пакетов" msgstr "Ошибка при получении пакетов"
#: info.go:76 #: info.go:83
msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов"
#: info.go:90
msgid "Command info expected at least 1 argument, got %d" msgid "Command info expected at least 1 argument, got %d"
msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d"
#: info.go:110 #: info.go:104
msgid "Error finding packages" msgid "Error finding packages"
msgstr "Ошибка при поиске пакетов" msgstr "Ошибка при поиске пакетов"
#: info.go:124 #: info.go:118
msgid "Can't detect system language" msgid "Can't detect system language"
msgstr "Ошибка при определении языка системы" msgstr "Ошибка при определении языка системы"
#: info.go:141 #: info.go:135
msgid "Error resolving overrides" msgid "Error resolving overrides"
msgstr "Ошибка устранения переорпеделений" msgstr "Ошибка устранения переорпеделений"
#: info.go:149 info.go:154 #: info.go:144 info.go:149
msgid "Error encoding script variables" msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита" msgstr "Ошибка кодирования переменных скрита"
#: install.go:40 #: install.go:39
msgid "Install a new package" msgid "Install a new package"
msgstr "Установить новый пакет" msgstr "Установить новый пакет"
#: install.go:52 #: install.go:51
msgid "Command install expected at least 1 argument, got %d" msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
#: install.go:114 #: install.go:113
msgid "Error when installing the package" msgid "Error when installing the package"
msgstr "Ошибка при установке пакета" msgstr "Ошибка при установке пакета"
#: install.go:159 #: install.go:151
msgid "Remove an installed package" msgid "Remove an installed package"
msgstr "Удалить установленный пакет" msgstr "Удалить установленный пакет"
#: install.go:178 #: install.go:170
msgid "Error listing installed packages" msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов" msgstr "Ошибка при составлении списка установленных пакетов"
#: install.go:215 #: install.go:199
msgid "Command remove expected at least 1 argument, got %d" msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d" msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
#: install.go:230 #: install.go:214
msgid "Error removing packages" msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов" 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 #: internal/cliutils/app_builder/builder.go:75
msgid "Error loading config" msgid "Error loading config"
msgstr "Ошибка при загрузке" msgstr "Ошибка при загрузке"
@ -193,15 +259,15 @@ msgstr "Ошибка при загрузке"
msgid "Error initialization database" msgid "Error initialization database"
msgstr "Ошибка инициализации базы данных" msgstr "Ошибка инициализации базы данных"
#: internal/cliutils/app_builder/builder.go:135 #: internal/cliutils/app_builder/builder.go:142
msgid "Error pulling repositories" msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев" msgstr "Ошибка при извлечении репозиториев"
#: internal/cliutils/app_builder/builder.go:152 #: internal/cliutils/app_builder/builder.go:159
msgid "Error parsing os release" msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы" 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" msgid "Unable to detect a supported package manager on the system"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
@ -293,11 +359,11 @@ msgstr ""
"Эта команда устарела и будет удалена в будущем, используйте вместо нее " "Эта команда устарела и будет удалена в будущем, используйте вместо нее "
"\"%s\"!" "\"%s\"!"
#: internal/db/db.go:137 #: internal/db/db.go:76
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"
msgstr "Несоответствие версий базы данных; сброс настроек" msgstr "Несоответствие версий базы данных; сброс настроек"
#: internal/db/db.go:144 #: internal/db/db.go:82
msgid "" msgid ""
"Database version does not exist. Run alr fix if something isn't working." "Database version does not exist. Run alr fix if something isn't working."
msgstr "" msgstr ""
@ -331,6 +397,26 @@ msgstr "%s %s загружается — %s/с\n"
msgid "ERROR" msgid "ERROR"
msgstr "ОШИБКА" 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 #: internal/utils/cmd.go:97
msgid "Error on dropping capabilities" msgid "Error on dropping capabilities"
msgstr "Ошибка при понижении привилегий" msgstr "Ошибка при понижении привилегий"
@ -359,11 +445,11 @@ msgstr "Ошибка при получении пакетов для обнов
msgid "No packages for upgrade" msgid "No packages for upgrade"
msgstr "Нет пакетов к обновлению" msgstr "Нет пакетов к обновлению"
#: list.go:102 list.go:187 #: list.go:102 list.go:184
msgid "Error parsing format template" msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона" msgstr "Ошибка при разборе шаблона"
#: list.go:108 list.go:191 #: list.go:108 list.go:188
msgid "Error executing template" msgid "Error executing template"
msgstr "Ошибка при выполнении шаблона" msgstr "Ошибка при выполнении шаблона"
@ -387,92 +473,6 @@ msgstr "Показать справку"
msgid "Error while running app" msgid "Error while running app"
msgstr "Ошибка при запуске приложения" 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 #: refresh.go:30
msgid "Pull all repositories that have changed" msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории" msgstr "Скачать все изменённые репозитории"
@ -561,18 +561,25 @@ msgstr "Иcкать по provides"
msgid "Error while executing search" msgid "Error while executing search"
msgstr "Ошибка при выполнении поиска" msgstr "Ошибка при выполнении поиска"
#: upgrade.go:47 #: upgrade.go:48
msgid "Upgrade all installed packages" msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты" msgstr "Обновить все установленные пакеты"
#: upgrade.go:105 upgrade.go:122 #: upgrade.go:106 upgrade.go:123
msgid "Error checking for updates" msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений" msgstr "Ошибка при проверке обновлений"
#: upgrade.go:125 #: upgrade.go:126
msgid "There is nothing to do." msgid "There is nothing to do."
msgstr "Здесь нечего делать." msgstr "Здесь нечего делать."
#, fuzzy
#~ msgid "Failed to clear contents of cache directory"
#~ msgstr "Не удалось создать каталог кэша репозитория"
#~ msgid "Error iterating over packages"
#~ msgstr "Ошибка при переборе пакетов"
#~ msgid "Error pulling repos" #~ msgid "Error pulling repos"
#~ msgstr "Ошибка при извлечении репозиториев" #~ msgstr "Ошибка при извлечении репозиториев"
@ -588,9 +595,6 @@ msgstr "Здесь нечего делать."
#~ msgid "Unable to create config directory" #~ msgid "Unable to create config directory"
#~ msgstr "Не удалось создать каталог конфигурации ALR" #~ msgstr "Не удалось создать каталог конфигурации ALR"
#~ msgid "Unable to create repo cache directory"
#~ msgstr "Не удалось создать каталог кэша репозитория"
#~ msgid "Unable to create package cache directory" #~ msgid "Unable to create package cache directory"
#~ msgstr "Не удалось создать каталог кэша пакетов" #~ msgstr "Не удалось создать каталог кэша пакетов"

15
list.go
View File

@ -29,12 +29,12 @@ import (
"github.com/leonelquinteros/gotext" "github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
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/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/manager"
) )
func ListCmd() *cli.Command { func ListCmd() *cli.Command {
@ -69,9 +69,9 @@ func ListCmd() *cli.Command {
WithConfig(). WithConfig().
WithDB(). WithDB().
WithManager(). WithManager().
WithDistroInfo().
// autoPull only // autoPull only
WithRepos(). WithRepos().
WithDistroInfo().
Build() Build()
if err != nil { if err != nil {
return err return err
@ -125,7 +125,6 @@ func ListCmd() *cli.Command {
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
} }
defer result.Close()
installedAlrPackages := map[string]string{} installedAlrPackages := map[string]string{}
if c.Bool("installed") { if c.Bool("installed") {
@ -150,9 +149,7 @@ func ListCmd() *cli.Command {
} }
} }
for result.Next() { for _, pkg := range result {
var pkg database.Package
err := result.StructScan(&pkg)
if err != nil { if err != nil {
return cli.Exit(err, 1) return cli.Exit(err, 1)
} }
@ -162,7 +159,7 @@ func ListCmd() *cli.Command {
} }
type packageInfo struct { type packageInfo struct {
Package *database.Package Package *alrsh.Package
Version string 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/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/translations" "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" "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.en.toml
./internal/translations/files/lure.ru.toml ./internal/translations/files/lure.ru.toml
./internal/translations/translations.go ./internal/translations/translations.go
./internal/types/build.go ./pkg/types/build.go
./internal/types/config.go ./pkg/types/config.go
./internal/types/repo.go ./pkg/types/repo.go
./list.go ./list.go
./main.go ./main.go
./pkg/build/build.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 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 { type Scripts struct {
PreInstall string `sh:"preinstall"` PreInstall string `sh:"preinstall"`
PostInstall string `sh:"postinstall"` 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" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
) )
func RepoCmd() *cli.Command { func RepoCmd() *cli.Command {

View File

@ -41,10 +41,10 @@ installPkg() {
fi fi
case $1 in case $1 in
pacman) $rootCmd pacman --noconfirm -U ${@:2} ;; pacman) $rootCmd pacman --noconfirm -U "${@:2}" ;;
apk) $rootCmd apk add --allow-untrusted ${@:2} ;; apk) $rootCmd apk add --allow-untrusted "${@:2}" ;;
zypper) $rootCmd zypper --no-gpg-checks install ${@:2} ;; zypper) $rootCmd zypper --no-gpg-checks install "${@:2}" ;;
*) $rootCmd $1 install -y ${@:2} ;; *) $rootCmd "$1" install -y "${@:2}" ;;
esac esac
} }
@ -88,60 +88,51 @@ else
fi fi
if [ -z "$noPkgMgr" ]; then if [ -z "$noPkgMgr" ]; then
info "Получение списка файлов с https://plemya-x.ru/" info "Получение списка файлов с https://gitea.plemya-x.ru/Plemya-x/ALR/releases"
pageContent=$(curl -s https://plemya-x.ru/?dir=alr)
# Изменено URL и регулярное выражение для списка файлов
pageContent=$(curl -s https://gitea.plemya-x.ru/Plemya-x/ALR/releases)
# Извлечение списка файлов из HTML # Извлечение списка файлов из 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 "Полученный список файлов:"
echo "$fileList" echo "$fileList"
if [ "$pkgMgr" == "pacman" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.pkg\.tar\.zst' | sort -V | tail -n 1) if [ "$pkgMgr" == "pacman" ]; then
elif [ "$pkgMgr" == "apt" ]; then latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.pkg\.tar\.zst' | sort -V | tail -n 1)
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.amd64\.deb' | sort -V | tail -n 1) elif [ "$pkgMgr" == "apt" ]; then
elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.amd64\.deb' | sort -V | tail -n 1)
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.x86_64\.rpm' | grep -v 'alt1' | sort -V | tail -n 1) elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then
elif [ "$pkgMgr" == "apt-get" ]; then latestFile=$(printf "%s\n" "${fileList[@]}" | grep -E 'alr-bin-.*\.x86_64\.rpm' | grep -v 'alt[0-9]*' | sort -V | tail -n 1)
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*-alt[0-9]+\.x86_64\.rpm' | 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 else
error "Не поддерживаемый менеджер пакетов для автоматической установки" echo "Не найден поддерживаемый менеджер пакетов. О_о"
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 "Все задачи выполнены!"
fi fi

View File

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

View File

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