2 Commits

Author SHA1 Message Date
cfb3764412 lint: run make fmt 2024-12-28 12:02:20 +03:00
df87bfcc63 lint: add golangci-lint 2024-12-28 12:02:05 +03:00
205 changed files with 5810 additions and 19823 deletions

View File

@@ -1,57 +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/>.
name: E2E
# on:
# push:
# branches: [ main ]
# pull_request:
on:
workflow_dispatch:
jobs:
tests:
runs-on: ubuntu-latest
container:
image: altlinux.space/maks1ms/actions-container-runner:latest
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: https://github.com/actions/setup-go@v5
with:
go-version: '1.24'
cache: false
# - name: Cache Podman images
# uses: actions/cache@v4
# with:
# path: |
# ~/.local/share/containers/storage
# /var/lib/containers/storage
# key: ${{ runner.os }}-primes
- name: Run E2E tests
env:
IGNORE_ROOT_CHECK: 1
run: |
make e2e-test

View File

@@ -1,51 +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/>.
name: Pre-commit
on:
push:
branches: [ master ]
pull_request:
jobs:
pre-commit:
runs-on: ubuntu-latest
container:
image: docker.gitea.com/runner-images:ubuntu-latest
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Set up Go
uses: https://github.com/actions/setup-go@v5
with:
go-version: '1.24'
- name: Set up Python for pre-commit
uses: https://github.com/actions/setup-python@v5
with:
python-version: '3.12'
- name: Install deps
run: apt-get update && apt-get install -y gettext bc
- run: pip install pre-commit
- run: pre-commit install
- run: pre-commit run --all-files

View File

@@ -1,134 +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/>.
name: Create Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout this repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: Get Changes between Tags
id: changes
uses: simbo/changes-between-tags-action@v1
- name: Set version
run: |
version=$(echo "${GITHUB_REF##*/}" | sed 's/^v//')
echo "Version - $version"
echo "VERSION=$version" >> $GITHUB_ENV
- name: Prepare for install
run: |
apt-get update
- name: Build alr
env:
IGNORE_ROOT_CHECK: 1
run: |
make build
- name: Create tar.gz
run: |
mkdir -p ./out/completion
cp alr ./out
cp scripts/completion/bash ./out/completion/alr
cp scripts/completion/zsh ./out/completion/_alr
( cd out && tar -czvf ../alr-${{ env.VERSION }}-linux-x86_64.tar.gz * )
- name: Release
uses: akkuman/gitea-release-action@v1
with:
body: ${{ steps.changes.outputs.changes }}
files: |-
alr-${{ env.VERSION }}-linux-x86_64.tar.gz
- name: Checkout alr-default repository
uses: actions/checkout@v4
with:
repository: Plemya-x/alr-default
token: ${{ secrets.GITEAPUBLIC }}
path: alr-default
- name: Calculate checksum
run: |
# Вычисляем SHA256 контрольную сумму архива
CHECKSUM=$(sha256sum alr-${{ env.VERSION }}-linux-x86_64.tar.gz | awk '{print $1}')
echo "Archive checksum: $CHECKSUM"
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV
- name: Update version and checksum in alr-bin
run: |
# Обновляем версию
sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh
sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh
# Обновляем контрольную сумму
sed -i "s/checksums=('[^']*')/checksums=('${{ env.CHECKSUM }}')/g" alr-default/alr-bin/alr.sh
- name: Commit and push changes to alr-default
run: |
cd alr-default
git config user.name "gitea"
git config user.email "admin@plemya-x.ru"
git add alr-bin/alr.sh
git commit -m "Обновление alr-bin до версии ${{ env.VERSION }}"
git push
- name: Install alr
env:
CREATE_SYSTEM_RESOURCES: 0
run: |
make install
- name: Prepare directories for ALR
run: |
# Создаём необходимые директории для работы alr build
mkdir -p /tmp/alr/dl /tmp/alr/pkgs /var/cache/alr
chmod -R 777 /tmp/alr
chmod -R 755 /var/cache/alr
- 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*.deb
alr-bin*.rpm
alr-bin*.pkg.tar.zst

14
.gitignore vendored
View File

@@ -3,14 +3,6 @@
/cmd/alr-api-server/alr-api-server /cmd/alr-api-server/alr-api-server
/dist/ /dist/
/internal/config/version.txt /internal/config/version.txt
.fleet/ .fleet
.idea/ .idea
.gigaide/ .gigaide
*.out
e2e-tests/alr
CLAUDE.md
commit_msg.txt
/scripts/.claude/settings.local.json
/ALR

View File

@@ -1,25 +1,9 @@
# 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/>.
run: run:
timeout: 5m timeout: 5m
linters-settings: linters-settings:
goimports: goimports:
local-prefixes: "gitea.plemya-x.ru/Plemya-x/ALR" local-prefixes: "plemya-x.ru/alr"
gofmt: gofmt:
simplify: true simplify: true
gofumpt: gofumpt:
@@ -36,18 +20,7 @@ linters:
- unused - unused
- errcheck - errcheck
- typecheck - typecheck
- wrapcheck # - forbidigo
issues: issues:
fix: true fix: true
exclude-rules:
- linters:
- wrapcheck
path-except: "internal/repos/find.go"
- path: _test\.go
linters:
- errcheck
# TODO: remove
- linters:
- staticcheck
text: "SA1019: interp.ExecHandler"

99
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,99 @@
before:
hooks:
- go mod tidy
builds:
- id: alr
env:
- CGO_ENABLED=0
binary: alr
ldflags:
- -X gitea.plemya-x.ru/xpamych/ALR/src/branch/master/internal/config.Version={{.Version}}
goos:
- linux
goarch:
- amd64
- 386
- arm64
- arm
- riscv64
archives:
- name_template: >-
{{- .ProjectName}}-
{{- .Version}}-
{{- .Os}}-
{{- if .Arch | eq "amd64"}}x86_64
{{- else if .Arch | eq "386"}}i386
{{- else if .Arch | eq "arm64"}}aarch64
{{- else }}{{ .Arch }}{{ end -}}
files:
- scripts/completion/*
nfpms:
- id: alr
package_name: linux-user-repository
file_name_template: >-
{{- .PackageName}}-
{{- .Version}}-
{{- .Os}}-
{{- if .Arch | eq "amd64"}}x86_64
{{- else if .Arch | eq "386"}}i386
{{- else if .Arch | eq "arm64"}}aarch64
{{- else }}{{ .Arch }}{{ end -}}
description: "Any Linux Repository"
homepage: 'https://gitea.plemya-x.ru/xpamych/ALR'
maintainer: 'Евгений Храмов <xpamych@yandex.ru>'
license: GPLv3
formats:
- apk
- deb
- rpm
- archlinux
provides:
- linux-user-repository
conflicts:
- linux-user-repository
recommends:
- aria2
contents:
- src: scripts/completion/bash
dst: /usr/share/bash-completion/completions/alr
- src: scripts/completion/zsh
dst: /usr/share/zsh/site-functions/_alr
aurs:
- name: linux-user-repository-bin
homepage: 'https://gitea.plemya-x.ru/xpamych/ALR'
description: "Any Linux Repository"
maintainers:
- 'Евгений Храмов <xpamych@yandex.ru>'
license: GPLv3
private_key: '{{ .Env.AUR_KEY }}'
git_url: 'ssh://aur@aur.archlinux.org/linux-user-repository-bin.git'
provides:
- alr
conflicts:
- alr
depends:
- sudo
- pacman
optdepends:
- 'aria2: for downloading torrent sources'
package: |-
# binaries
install -Dm755 ./alr "${pkgdir}/usr/bin/alr"
# completions
install -Dm755 ./scripts/completion/bash ${pkgdir}/usr/share/bash-completion/completions/alr
install -Dm755 ./scripts/completion/zsh ${pkgdir}/usr/share/zsh/site-functions/_alr
release:
gitea:
owner: alr
name: alr
gitea_urls:
api: 'https://gitea.elara.ws/api/v1/'
download: 'https://gitea.elara.ws'
skip_tls_verify: false
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc

View File

@@ -1,43 +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/>.
repos:
- repo: local
hooks:
- id: test-coverage
name: Run test coverage
entry: bash scripts/test-coverage-precommit.sh
language: system
pass_filenames: false
- id: fmt
name: Format code
entry: bash scripts/fmt-precommit.sh
language: system
pass_filenames: false
- id: update-license
name: Update license
entry: make update-license
language: system
pass_filenames: false
- id: i18n
name: Update i18n
entry: bash scripts/i18n-precommit.sh
language: system
pass_filenames: false
always_run: true

9
.woodpecker.yml Normal file
View File

@@ -0,0 +1,9 @@
platform: linux/amd64
pipeline:
release:
image: goreleaser/goreleaser
commands:
- goreleaser release
secrets: [ gitea_token, aur_key ]
when:
event: tag

View File

@@ -1,2 +0,0 @@
Евгений Храмов
Maxim Slipenko

View File

@@ -1,24 +1,17 @@
NAME := alr NAME := alr
GIT_VERSION ?= $(shell git describe --tags ) GIT_VERSION = $(shell git describe --tags )
IGNORE_ROOT_CHECK ?= 0
DESTDIR ?= DESTDIR ?=
PREFIX ?= /usr/local PREFIX ?= /usr/local
BIN := ./$(NAME) BIN := ./$(NAME)
INSTALLED_BIN := $(DESTDIR)/$(PREFIX)/bin/$(NAME) INSTALED_BIN := $(DESTDIR)/$(PREFIX)/bin/$(NAME)
COMPLETIONS_DIR := ./scripts/completion COMPLETIONS_DIR := ./scripts/completion
BASH_COMPLETION := $(COMPLETIONS_DIR)/bash BASH_COMPLETION := $(COMPLETIONS_DIR)/bash
ZSH_COMPLETION := $(COMPLETIONS_DIR)/zsh ZSH_COMPLETION := $(COMPLETIONS_DIR)/zsh
INSTALLED_BASH_COMPLETION := $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(NAME) INSTALLED_BASH_COMPLETION := $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(NAME)
INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME) INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME)
GENERATE ?= 1 GOLANGCI_LINT := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
CREATE_SYSTEM_RESOURCES ?= 1
ROOT_DIRS := /var/cache/alr /etc/alr
ADD_LICENSE_BIN := go run github.com/google/addlicense@4caba19b7ed7818bb86bc4cd20411a246aa4a524
GOLANGCI_LINT_BIN := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4
XGOTEXT_BIN := go run github.com/Tom5521/xgotext@v1.2.0
.PHONY: build install clean clear uninstall check-no-root .PHONY: build install clean clear uninstall check-no-root
@@ -26,36 +19,26 @@ build: check-no-root $(BIN)
export CGO_ENABLED := 0 export CGO_ENABLED := 0
$(BIN): $(BIN):
ifeq ($(GENERATE),1) go build -ldflags="-X 'gitea.plemya-x.ru/xpamych/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
go generate ./...
else
@echo "Skipping go generate (GENERATE=0)"
endif
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 [[ "$$(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; \
exit 1; \ exit 1; \
fi fi
# TODO: remove --tests=false
fmt:
$(GOLANGCI_LINT) run --fix --tests=false
install: \ install: \
$(INSTALLED_BIN) \ $(INSTALED_BIN) \
$(INSTALLED_BASH_COMPLETION) \ $(INSTALLED_BASH_COMPLETION) \
$(INSTALLED_ZSH_COMPLETION) $(INSTALLED_ZSH_COMPLETION)
@echo "Installation done!" @echo "Installation done!"
$(INSTALLED_BIN): $(BIN) $(INSTALED_BIN): $(BIN)
install -Dm755 $< $@ install -Dm755 $< $@
ifeq ($(CREATE_SYSTEM_RESOURCES),1)
@for dir in $(ROOT_DIRS); do \
install -d -m 775 $$dir; \
chgrp wheel $$dir; \
done
else
@echo "Skipping root dir creation (CREATE_SYSTEM_RESOURCES=0)"
endif
$(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION) $(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION)
install -Dm755 $< $@ install -Dm755 $< $@
@@ -65,38 +48,9 @@ $(INSTALLED_ZSH_COMPLETION): $(ZSH_COMPLETION)
uninstall: uninstall:
rm -f \ rm -f \
$(INSTALLED_BIN) \ $(INSTALED_BIN) \
$(INSTALLED_BASH_COMPLETION) \ $(INSTALLED_BASH_COMPLETION) \
$(INSTALLED_ZSH_COMPLETION) $(INSTALLED_ZSH_COMPLETION)
clean clear: clean clear:
rm -f $(BIN) rm -f $(BIN)
OLD_FILES=$(shell cat old-files)
IGNORE_OLD_FILES := $(foreach file,$(shell cat old-files),-ignore $(file))
update-license:
$(ADD_LICENSE_BIN) -v -f license-header-old-files.tmpl $(OLD_FILES)
$(ADD_LICENSE_BIN) -v -f license-header.tmpl $(IGNORE_OLD_FILES) .
fmt:
$(GOLANGCI_LINT_BIN) run --fix
i18n:
$(XGOTEXT_BIN) --output ./internal/translations/default.pot
msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot
msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot
bash scripts/i18n-badge.sh
test-coverage:
go test -tags=test ./... -v -coverpkg=./... -coverprofile=coverage.out
bash scripts/coverage-badge.sh
update-deps-cve:
bash scripts/update-deps-cve.sh
prepare-for-e2e-test: clean build
rm -f ./e2e-tests/alr
cp alr e2e-tests
e2e-test: prepare-for-e2e-test
go test -tags=e2e ./...

View File

@@ -3,13 +3,11 @@
</p> </p>
<b></b> <b></b>
[![Go Report Card](https://goreportcard.com/badge/gitea.plemya-x.ru/Plemya-x/ALR)](https://goreportcard.com/report/gitea.plemya-x.ru/Plemya-x/ALR) ![Test coverage](./assets/coverage-badge.svg) ![ru translate](./assets/i18n-ru-badge.svg)
# ALR (Any Linux Repository) # ALR (Any Linux Repository)
ALR - это независимая от дистрибутива система сборки для Linux (форк [LURE](https://github.com/lure-sh/lure), аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. ALR готов к общему использованию, но все еще может время от времени ломаться или изменяться. ALR - это независимая от дистрибутива система сборки для Linux, аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. alr готов к общему использованию, но все еще может время от времени ломаться или заменяться.
ALR написан на чистом Go и после сборки не имеет зависимостей. Для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `apt-get` `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе используется поддерживаемый менеджер пакетов, то он будет обнаружен и использован автоматически. ALR написан на чистом Go и после сборки не имеет зависимостей. Единственное, для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе существует поддерживаемый менеджер пакетов, он будет обнаружен и использован автоматически.
--- ---
@@ -20,17 +18,17 @@ ALR написан на чистом Go и после сборки не имее
Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду: Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду:
```bash ```bash
curl -fsSL https://gitea.plemya-x.ru/Plemya-x/ALR/raw/branch/master/scripts/install.sh | bash curl -fsSL plemya-x.ru/alr/install.sh | bash
``` ```
**ВАЖНО**: При этом скрипт будет загружен и запущен [скрипт](https://gitea.plemya-x.ru/Plemya-x/ALR/src/branch/master/scripts/install.sh). Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. **ВАЖНО**: При этом скрипт будет загружен и запущен с <https://gitea.plemya-x.ru/xpamych/ALR/install>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их.
### Сборка из исходного кода ### Сборка из исходного кода
Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите: Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите:
```shell ```shell
make build -B make build
sudo make install sudo make install
``` ```
@@ -44,29 +42,20 @@ ALR был создан потому, что упаковка программн
## Документация ## Документация
Документация находится в [Wiki](https://alr.plemya-x.ru/wiki/ALR). Документация по всем этим вопросам находится в [Wiki](https://gitea.plemya-x.ru/xpamych/ALR/wiki/Home).
--- ---
## Репозитории ## Репозитории
Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD. Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD. Репозиторий [по-умолчанию](https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git).
Например, репозиторий с ALR [alr-default](https://gitea.plemya-x.ru/Plemya-x/alr-default.git)
```
alr repo add alr-default https://gitea.plemya-x.ru/Plemya-x/alr-default.git
```
Репозиторий пакетов [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 [alr-LG](https://gitea.plemya-x.ru/Plemya-x/alr-LG.git) можно подключить так:
```
alr repo add alr-LG https://git.linux-gaming.ru/Linux-Gaming/alr-LG.git
```
--- ---
## Соцсети ## Соцсети
VK - https://vk.com/plemya_kh
Discord - https://discord.com/channels/817759634105827358/1261631565084233749
Telegram - https://t.me/plemyakh Telegram - https://t.me/plemyakh
## Спасибы ## Спасибы
@@ -79,6 +68,3 @@ Telegram - https://t.me/plemyakh
- <https://github.com/goreleaser/nfpm> - <https://github.com/goreleaser/nfpm>
- <https://github.com/charmbracelet/bubbletea> - <https://github.com/charmbracelet/bubbletea>
- <https://gitlab.com/cznic/sqlite> - <https://gitlab.com/cznic/sqlite>
Благодарим за активное участие в развитии проекта:
- Maks1mS <maxim@slipenko.com>

View File

@@ -1,17 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="109" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<mask id="round">
<rect width="109" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)"><rect width="65" height="20" fill="#555"/>
<rect x="65" width="44" height="20" fill="#e05d44"/>
<rect width="109" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">18.9%</text>
<text x="86" y="14">18.9%</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 926 B

View File

@@ -1,18 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="129" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<mask id="round">
<rect width="129" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)">
<rect width="75" height="20" fill="#555"/>
<rect x="75" width="64" height="20" fill="#4c1"/>
<rect width="129" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text>
<text x="37" y="14">ru translate</text>
<text x="100" y="15" fill="#010101" fill-opacity=".3">100.00%</text>
<text x="100" y="14">100.00%</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 942 B

291
build.go
View File

@@ -1,218 +1,101 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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 main package main
import ( import (
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" "plemya-x.ru/alr/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "plemya-x.ru/alr/internal/osutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" "plemya-x.ru/alr/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" "plemya-x.ru/alr/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "plemya-x.ru/alr/pkg/loggerctx"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" "plemya-x.ru/alr/pkg/manager"
"plemya-x.ru/alr/pkg/repos"
) )
func BuildCmd() *cli.Command { var buildCmd = &cli.Command{
return &cli.Command{ Name: "build",
Name: "build", Usage: "Build a local package",
Usage: gotext.Get("Build a local package"), Flags: []cli.Flag{
Flags: []cli.Flag{ &cli.StringFlag{
&cli.StringFlag{ Name: "script",
Name: "script", Aliases: []string{"s"},
Aliases: []string{"s"}, Value: "alr.sh",
Value: "alr.sh", Usage: "Path to the build script",
Usage: gotext.Get("Path to the build script"),
},
&cli.StringFlag{
Name: "subpackage",
Aliases: []string{"sb"},
Usage: gotext.Get("Specify subpackage in script (for multi package script only)"),
},
&cli.StringFlag{
Name: "package",
Aliases: []string{"p"},
Usage: gotext.Get("Name of the package to build and its repo (example: default/go-bin)"),
},
&cli.BoolFlag{
Name: "clean",
Aliases: []string{"c"},
Usage: gotext.Get("Build package from scratch even if there's an already built package available"),
},
}, },
Action: func(c *cli.Context) error { &cli.StringFlag{
if err := utils.CheckUserPrivileges(); err != nil { Name: "package",
return err Aliases: []string{"p"},
} Usage: "Name of the package to build and its repo (example: default/go-bin)",
wd, err := os.Getwd()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
}
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithReposNoPull().
WithDistroInfo().
WithManager().
Build()
if err != nil {
return cli.Exit(err, 1)
}
defer deps.Defer()
var script string
var packages []string
var res []*build.BuiltDep
var scriptArgs *build.BuildPackageFromScriptArgs
var dbArgs *build.BuildPackageFromDbArgs
buildArgs := &build.BuildArgs{
Opts: &types.BuildOpts{
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
},
PkgFormat_: build.GetPkgFormat(deps.Manager),
Info: deps.Info,
}
switch {
case c.IsSet("script"):
script, err = filepath.Abs(c.String("script"))
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Cannot get absolute script path"), err)
}
subpackage := c.String("subpackage")
if subpackage != "" {
packages = append(packages, subpackage)
}
scriptArgs = &build.BuildPackageFromScriptArgs{
Script: script,
Packages: packages,
BuildArgs: *buildArgs,
}
case c.IsSet("package"):
// TODO: handle multiple packages
packageInput := c.String("package")
pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageInput})
if err != nil {
return cliutils.FormatCliExit("failed to find pkgs", err)
}
pkg := cliutils.FlattenPkgs(ctx, pkgs, "build", c.Bool("interactive"))
if len(pkg) < 1 {
return cliutils.FormatCliExit(gotext.Get("Package not found"), nil)
}
if pkg[0].BasePkgName != "" {
packages = append(packages, pkg[0].Name)
}
dbArgs = &build.BuildPackageFromDbArgs{
Package: &pkg[0],
Packages: packages,
BuildArgs: *buildArgs,
}
default:
return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil)
}
installer, installerClose, err := build.GetSafeInstaller()
if err != nil {
return err
}
defer installerClose()
scripter, scripterClose, err := build.GetSafeScriptExecutor()
if err != nil {
return err
}
defer scripterClose()
builder, err := build.NewMainBuilder(
deps.Cfg,
deps.Manager,
deps.Repos,
scripter,
installer,
)
if err != nil {
return err
}
if scriptArgs != nil {
res, err = builder.BuildPackageFromScript(
ctx,
scriptArgs,
)
} else if dbArgs != nil {
res, err = builder.BuildPackageFromDb(
ctx,
dbArgs,
)
}
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error building package"), err)
}
for _, pkg := range res {
name := filepath.Base(pkg.Path)
// Проверяем, существует ли файл перед перемещением
if _, err := os.Stat(pkg.Path); os.IsNotExist(err) {
slog.Info("Package file already moved or removed, skipping", "path", pkg.Path)
continue
}
err = osutils.Move(pkg.Path, filepath.Join(wd, name))
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err)
}
}
slog.Info(gotext.Get("Done"))
return nil
}, },
} &cli.BoolFlag{
Name: "clean",
Aliases: []string{"c"},
Usage: "Build package from scratch even if there's an already built package available",
},
},
Action: func(c *cli.Context) error {
ctx := c.Context
log := loggerctx.From(ctx)
script := c.String("script")
if c.String("package") != "" {
script = filepath.Join(config.GetPaths(ctx).RepoDir, c.String("package"), "alr.sh")
}
err := repos.Pull(ctx, config.Config(ctx).Repos)
if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send()
}
mgr := manager.Detect()
if mgr == nil {
log.Fatal("Unable to detect a supported package manager on the system").Send()
}
pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{
Script: script,
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
})
if err != nil {
log.Fatal("Error building package").Err(err).Send()
}
wd, err := os.Getwd()
if err != nil {
log.Fatal("Error getting working directory").Err(err).Send()
}
for _, pkgPath := range pkgPaths {
name := filepath.Base(pkgPath)
err = osutils.Move(pkgPath, filepath.Join(wd, name))
if err != nil {
log.Fatal("Error moving the package").Err(err).Send()
}
}
return nil
},
} }

236
config.go
View File

@@ -1,236 +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 main
import (
"fmt"
"strconv"
"strings"
"github.com/goccy/go-yaml"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
)
func ConfigCmd() *cli.Command {
return &cli.Command{
Name: "config",
Usage: gotext.Get("Manage config"),
Subcommands: []*cli.Command{
ShowCmd(),
SetConfig(),
GetConfig(),
},
}
}
func ShowCmd() *cli.Command {
return &cli.Command{
Name: "show",
Usage: gotext.Get("Show config"),
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
return nil
}),
Action: func(c *cli.Context) error {
deps, err := appbuilder.
New(c.Context).
WithConfig().
Build()
if err != nil {
return err
}
defer deps.Defer()
content, err := deps.Cfg.ToYAML()
if err != nil {
return err
}
fmt.Println(content)
return nil
},
}
}
var configKeys = []string{
"rootCmd",
"useRootCmd",
"pagerStyle",
"autoPull",
"logLevel",
"ignorePkgUpdates",
"updateSystemOnUpgrade",
}
func SetConfig() *cli.Command {
return &cli.Command{
Name: "set",
Usage: gotext.Get("Set config value"),
ArgsUsage: gotext.Get("<key> <value>"),
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
if c.Args().Len() == 0 {
for _, key := range configKeys {
fmt.Println(key)
}
return nil
}
return nil
}),
Action: utils.RootNeededAction(func(c *cli.Context) error {
if c.Args().Len() < 2 {
return cliutils.FormatCliExit("missing args", nil)
}
key := c.Args().Get(0)
value := c.Args().Get(1)
deps, err := appbuilder.
New(c.Context).
WithConfig().
Build()
if err != nil {
return err
}
defer deps.Defer()
switch key {
case "rootCmd":
deps.Cfg.System.SetRootCmd(value)
case "useRootCmd":
boolValue, err := strconv.ParseBool(value)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("invalid boolean value for %s: %s", key, value), err)
}
deps.Cfg.System.SetUseRootCmd(boolValue)
case "pagerStyle":
deps.Cfg.System.SetPagerStyle(value)
case "autoPull":
boolValue, err := strconv.ParseBool(value)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("invalid boolean value for %s: %s", key, value), err)
}
deps.Cfg.System.SetAutoPull(boolValue)
case "logLevel":
deps.Cfg.System.SetLogLevel(value)
case "ignorePkgUpdates":
var updates []string
if value != "" {
updates = strings.Split(value, ",")
for i, update := range updates {
updates[i] = strings.TrimSpace(update)
}
}
deps.Cfg.System.SetIgnorePkgUpdates(updates)
case "updateSystemOnUpgrade":
boolValue, err := strconv.ParseBool(value)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("invalid boolean value for %s: %s", key, value), err)
}
deps.Cfg.System.SetUpdateSystemOnUpgrade(boolValue)
case "repo", "repos":
return cliutils.FormatCliExit(gotext.Get("use 'repo add/remove' commands to manage repositories"), nil)
default:
return cliutils.FormatCliExit(gotext.Get("unknown config key: %s", key), nil)
}
if err := deps.Cfg.System.Save(); err != nil {
return cliutils.FormatCliExit(gotext.Get("failed to save config"), err)
}
fmt.Println(gotext.Get("Successfully set %s = %s", key, value))
return nil
}),
}
}
func GetConfig() *cli.Command {
return &cli.Command{
Name: "get",
Usage: gotext.Get("Get config value"),
ArgsUsage: gotext.Get("<key>"),
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
if c.Args().Len() == 0 {
for _, key := range configKeys {
fmt.Println(key)
}
return nil
}
return nil
}),
Action: func(c *cli.Context) error {
deps, err := appbuilder.
New(c.Context).
WithConfig().
Build()
if err != nil {
return err
}
defer deps.Defer()
if c.Args().Len() == 0 {
content, err := deps.Cfg.ToYAML()
if err != nil {
return cliutils.FormatCliExit("failed to serialize config", err)
}
fmt.Print(content)
return nil
}
key := c.Args().Get(0)
switch key {
case "rootCmd":
fmt.Println(deps.Cfg.RootCmd())
case "useRootCmd":
fmt.Println(deps.Cfg.UseRootCmd())
case "pagerStyle":
fmt.Println(deps.Cfg.PagerStyle())
case "autoPull":
fmt.Println(deps.Cfg.AutoPull())
case "logLevel":
fmt.Println(deps.Cfg.LogLevel())
case "ignorePkgUpdates":
updates := deps.Cfg.IgnorePkgUpdates()
if len(updates) == 0 {
fmt.Println("[]")
} else {
fmt.Println(strings.Join(updates, ", "))
}
case "updateSystemOnUpgrade":
fmt.Println(deps.Cfg.UpdateSystemOnUpgrade())
case "repo", "repos":
repos := deps.Cfg.Repos()
if len(repos) == 0 {
fmt.Println("[]")
} else {
repoData, err := yaml.Marshal(repos)
if err != nil {
return cliutils.FormatCliExit("failed to serialize repos", err)
}
fmt.Print(string(repoData))
}
default:
return cliutils.FormatCliExit(gotext.Get("unknown config key: %s", key), nil)
}
return nil
},
}
}

View File

@@ -1,5 +0,0 @@
- name: alr-repo
url: https://gitea.plemya-x.ru/Plemya-x/repo-for-tests
ref: main
mirrors:
- https://github.com/example/example.git

View File

@@ -1,2 +0,0 @@
alr-repo/bar-pkg 1.0.0-1
alr-repo/foo-pkg 1.0.0-1

View File

@@ -1,42 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EAlrAddRepo(t *testing.T) {
runMatrixSuite(
t,
"add-repo-remove-repo",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
execShouldNoError(t, r, "sudo", "alr", "addrepo", "--name", "alr-repo", "--url", "https://gitea.plemya-x.ru/Plemya-x/alr-repo.git")
execShouldNoError(t, r, "bash", "-c", "cat /etc/alr/alr.toml")
execShouldNoError(t, r, "sudo", "alr", "removerepo", "--name", "alr-repo")
r.Command("bash", "-c", "cat /etc/alr/alr.toml").
ExpectStdoutContains("repo = []").
Run(t)
},
)
}

View File

@@ -1,36 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EBashCompletion(t *testing.T) {
runMatrixSuite(
t,
"bash-completion",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
execShouldNoError(t, r, "alr", "install", "--generate-bash-completion")
},
)
}

View File

@@ -1,96 +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/>.
//go:build e2e
package e2etests_test
import (
"fmt"
"testing"
"go.alt-gnome.ru/capytest"
"go.alt-gnome.ru/capytest/providers/podman"
)
var ALL_SYSTEMS []string = []string{
"ubuntu-24.04",
"alt-sisyphus",
"fedora-41",
// "archlinux",
// "alpine",
// "opensuse-leap",
// "redos-8",
}
var AUTOREQ_AUTOPROV_SYSTEMS []string = []string{
// "alt-sisyphus",
"fedora-41",
}
var RPM_SYSTEMS []string = []string{
"fedora-41",
}
var COMMON_SYSTEMS []string = []string{
"ubuntu-24.04",
}
func execShouldNoError(t *testing.T, r capytest.Runner, cmd string, args ...string) {
t.Helper()
r.Command(cmd, args...).ExpectSuccess().Run(t)
}
func execShouldError(t *testing.T, r capytest.Runner, cmd string, args ...string) {
t.Helper()
r.Command(cmd, args...).ExpectFailure().Run(t)
}
const REPO_NAME_FOR_E2E_TESTS = "alr-repo"
const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git"
func defaultPrepare(t *testing.T, r capytest.Runner) {
execShouldNoError(t, r,
"sudo",
"alr",
"repo",
"add",
REPO_NAME_FOR_E2E_TESTS,
REPO_URL_FOR_E2E_TESTS,
)
execShouldNoError(t, r,
"sudo",
"alr",
"ref",
)
}
func runMatrixSuite(t *testing.T, name string, images []string, test func(t *testing.T, r capytest.Runner)) {
t.Helper()
for _, image := range images {
ts := capytest.NewTestSuite(t, podman.Provider(
podman.WithImage(fmt.Sprintf("ghcr.io/maks1ms/alr-e2e-test-image-%s", image)),
podman.WithVolumes("./alr:/tmp/alr"),
podman.WithPrivileged(true),
))
ts.BeforeEach(func(t *testing.T, r capytest.Runner) {
execShouldNoError(t, r, "/bin/alr-test-setup", "alr-install")
execShouldNoError(t, r, "/bin/alr-test-setup", "passwordless-sudo-setup")
})
ts.Run(fmt.Sprintf("%s/%s", name, image), test)
}
}

View File

@@ -1,41 +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/>.
//go:build e2e
package e2etests_test
import (
"fmt"
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EFirejailedPackage(t *testing.T) {
runMatrixSuite(
t,
"firejailed-package",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
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

@@ -1,34 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EAlrFix(t *testing.T) {
runMatrixSuite(t, "run-fix", COMMON_SYSTEMS, func(t *testing.T, r capytest.Runner) {
r.Command("alr", "fix").
ExpectStderrContains("--> Done").
ExpectSuccess().
Run(t)
})
}

View File

@@ -1,38 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EGroupAndSummaryField(t *testing.T) {
runMatrixSuite(
t,
"group-and-summary-field",
RPM_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group.Resolved}}\" | grep ^System/Base$")
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary.Resolved}}\" | grep \"^Custom summary$\"")
},
)
}

View File

@@ -1,40 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue129RepoTomlImportTest(t *testing.T) {
runMatrixSuite(
t,
"issue-129-repo-toml-import-test",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
r.Command("alr", "config", "get", "repos").
ExpectStdoutMatchesSnapshot().
Run(t)
},
)
}

View File

@@ -1,63 +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/>.
//go:build e2e
package e2etests_test
import (
"fmt"
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue130Install(t *testing.T) {
runMatrixSuite(
t,
"alr install {repo}/{package}",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
t.Parallel()
defaultPrepare(t, r)
r.Command("sudo", "alr", "in", fmt.Sprintf("%s/foo-pkg", REPO_NAME_FOR_E2E_TESTS)).
ExpectSuccess().
Run(t)
r.Command("sudo", "alr", "in", fmt.Sprintf("%s/bar-pkg", "NOT_REPO_NAME_FOR_E2E_TESTS")).
ExpectFailure().
Run(t)
},
)
runMatrixSuite(
t,
"alr install {package}+{repo}",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
t.Parallel()
defaultPrepare(t, r)
r.Command("sudo", "alr", "in", fmt.Sprintf("foo-pkg+%s", REPO_NAME_FOR_E2E_TESTS)).
ExpectSuccess().
Run(t)
r.Command("sudo", "alr", "in", fmt.Sprintf("bar-pkg+%s", "NOT_REPO_NAME_FOR_E2E_TESTS")).
ExpectFailure().
Run(t)
},
)
}

View File

@@ -1,40 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue32Interactive(t *testing.T) {
runMatrixSuite(
t,
"issue-32-interactive",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
execShouldNoError(t, r, "alr", "--interactive=false", "remove", "ca-certificates")
execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "remove", "openssl")
execShouldNoError(t, r, "alr", "fix")
execShouldNoError(t, r, "sudo", "apt-get", "update")
execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "install", "ca-certificates")
},
)
}

View File

@@ -1,40 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue41AutoreqSkiplist(t *testing.T) {
runMatrixSuite(
t,
"issue-41-autoreq-skiplist",
AUTOREQ_AUTOPROV_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "alr", "build", "-p", "alr-repo/test-autoreq-autoprov")
execShouldNoError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/sh$\"")
execShouldError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/bash$\"")
execShouldError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/zsh$\"")
},
)
}

View File

@@ -1,39 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue50InstallMultiple(t *testing.T) {
runMatrixSuite(
t,
"issue-50-install-multiple",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg")
execShouldNoError(t, r, "cat", "/opt/foo")
execShouldNoError(t, r, "cat", "/opt/bar")
},
)
}

View File

@@ -1,37 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue53LcAllCInfo(t *testing.T) {
runMatrixSuite(
t,
"issue-53-lc-all-c-info",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "bash", "-c", "LANG=C alr info foo-pkg")
},
)
}

View File

@@ -1,40 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue59RmCompletion(t *testing.T) {
runMatrixSuite(
t,
"issue-59-rm-completion",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg")
execShouldNoError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^foo-pkg$")
execShouldNoError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^bar-pkg$")
execShouldError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^test-autoreq-autoprov$")
},
)
}

View File

@@ -1,50 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue62List(t *testing.T) {
runMatrixSuite(
t,
"issue-62-list",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
execShouldNoError(t, r, "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg")
r.Command("alr", "list", "-I").
ExpectSuccess().
ExpectStdoutMatchesSnapshot().
Run(t)
r.Command("alr", "list").
ExpectSuccess().
ExpectStdoutMatchesSnapshot().
Run(t)
},
)
}

View File

@@ -1,37 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue72InstallWithDeps(t *testing.T) {
runMatrixSuite(
t,
"issue-72-install-with-deps",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "test-app-with-lib")
},
)
}

View File

@@ -1,43 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue74Upgradable(t *testing.T) {
runMatrixSuite(
t,
"issue-74-upgradable",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
execShouldNoError(t, r, "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "in", "bar-pkg")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "d9a3541561")
execShouldNoError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1")
},
)
}

View File

@@ -1,38 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue75InstallWithDeps(t *testing.T) {
runMatrixSuite(
t,
"issue-75-ref-specify",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
execShouldNoError(t, r, "sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1")
},
)
}

View File

@@ -1,54 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func Test75SinglePackageRepo(t *testing.T) {
runMatrixSuite(
t,
"issue-76-single-package-repo",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
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", "ref")
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "1075c918be")
execShouldNoError(t, r, "alr", "fix")
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

@@ -1,49 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue78Mirrors(t *testing.T) {
runMatrixSuite(t, "issue-78-mirrors", COMMON_SYSTEMS, func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldNoError(t, r, "sudo", "alr", "repo", "set-url", REPO_NAME_FOR_E2E_TESTS, "https://example.com")
execShouldNoError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "clear", REPO_NAME_FOR_E2E_TESTS)
execShouldError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", "--partial", REPO_NAME_FOR_E2E_TESTS, "gitea.plemya-x.ru/Maks1mS")
execShouldError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldError(t, r, "sudo", "alr", "ref")
},
)
}

View File

@@ -1,38 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue81MultiplePackages(t *testing.T) {
runMatrixSuite(
t,
"issue-81-multiple-packages",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "first-package-with-dashes")
execShouldNoError(t, r, "cat", "/opt/first-package")
},
)
}

View File

@@ -1,40 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue91MultiplePackages(t *testing.T) {
runMatrixSuite(
t,
"issue-91-set-repo-ref",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldError(t, r, "sudo", "alr", "repo", "set-ref")
execShouldError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo")
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
execShouldNoError(t, r, "sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1")
},
)
}

View File

@@ -1,48 +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/>.
//go:build e2e
package e2etests_test
import (
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue94TwiceBuild(t *testing.T) {
runMatrixSuite(
t,
"issue-94-twice-build",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
var stderr bytes.Buffer
r.Command("sudo", "alr", "in", "test-94-app").
WithCaptureStderr(&stderr).
ExpectSuccess().
Run(t)
assert.Equal(t, 1, strings.Count(stderr.String(), "Building package name=test-94-dep"))
},
)
}

View File

@@ -1,47 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EIssue95ConfigCommand(t *testing.T) {
runMatrixSuite(
t,
"issue-95-config-command",
COMMON_SYSTEMS,
func(t *testing.T, r capytest.Runner) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sh", "-c", "alr config show | grep \"autoPull: true\"")
execShouldNoError(t, r, "sh", "-c", "alr config get | grep \"autoPull: true\"")
execShouldError(t, r, "sh", "-c", "cat /etc/alr/alr.toml | grep \"autoPull\"")
execShouldNoError(t, r, "alr", "config", "get", "autoPull")
execShouldError(t, r, "alr", "config", "set", "autoPull")
execShouldNoError(t, r, "sudo", "alr", "config", "set", "autoPull", "false")
execShouldNoError(t, r, "sh", "-c", "alr config show | grep \"autoPull: false\"")
execShouldNoError(t, r, "sh", "-c", "alr config get | grep \"autoPull: false\"")
execShouldNoError(t, r, "sh", "-c", "cat /etc/alr/alr.toml | grep \"autoPull = false\"")
execShouldNoError(t, r, "alr", "config", "set", "autoPull", "true")
execShouldNoError(t, r, "sh", "-c", "cat /etc/alr/alr.toml | grep \"autoPull = true\"")
},
)
}

View File

@@ -1,35 +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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"go.alt-gnome.ru/capytest"
)
func TestE2EAlrVersion(t *testing.T) {
runMatrixSuite(t, "version", COMMON_SYSTEMS, func(t *testing.T, r capytest.Runner) {
r.Command("alr", "version").
ExpectStderrRegex(`^v\d+\.\d+\.\d+(?:-\d+-g[a-f0-9]+)?\n$`).
ExpectStdoutEmpty().
ExpectSuccess().
Run(t)
})
}

240
fix.go
View File

@@ -1,219 +1,65 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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 main package main
import ( import (
"io/fs"
"log/slog"
"os" "os"
"os/exec"
"path/filepath"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "plemya-x.ru/alr/internal/config"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" "plemya-x.ru/alr/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "plemya-x.ru/alr/pkg/loggerctx"
"plemya-x.ru/alr/pkg/repos"
) )
// execWithPrivileges выполняет команду напрямую если root или CI, иначе через sudo var fixCmd = &cli.Command{
func execWithPrivileges(name string, args ...string) *exec.Cmd { Name: "fix",
isRoot := os.Geteuid() == 0 Usage: "Attempt to fix problems with ALR",
isCI := os.Getenv("CI") == "true" Action: func(c *cli.Context) error {
ctx := c.Context
if !isRoot && !isCI { log := loggerctx.From(ctx)
// Если не root и не в CI, используем sudo
allArgs := append([]string{name}, args...)
return exec.Command("sudo", allArgs...)
} else {
// Если root или в CI, запускаем напрямую
return exec.Command(name, args...)
}
}
func FixCmd() *cli.Command { db.Close()
return &cli.Command{ paths := config.GetPaths(ctx)
Name: "fix",
Usage: gotext.Get("Attempt to fix problems with ALR"),
Action: func(c *cli.Context) error {
// Команда выполняется от текущего пользователя
// При необходимости будет запрошен sudo для удаления файлов root
ctx := c.Context log.Info("Removing cache directory").Send()
deps, err := appbuilder. err := os.RemoveAll(paths.CacheDir)
New(ctx).
WithConfig().
Build()
if err != nil {
return cli.Exit(err, 1)
}
defer deps.Defer()
cfg := deps.Cfg
paths := cfg.GetPaths()
slog.Info(gotext.Get("Clearing cache and temporary directories"))
// Проверяем, существует ли директория кэша
dir, err := os.Open(paths.CacheDir)
if err != nil {
if os.IsNotExist(err) {
// Директория не существует, просто создадим её позже
slog.Info(gotext.Get("Cache directory does not exist, will create it"))
} else {
return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err)
}
} else {
defer dir.Close()
entries, err := dir.Readdirnames(-1)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err)
}
for _, entry := range entries {
fullPath := filepath.Join(paths.CacheDir, entry)
// Пробуем сделать файлы доступными для записи
if err := makeWritableRecursive(fullPath); err != nil {
slog.Debug("Failed to make path writable", "path", fullPath, "error", err)
}
// Пробуем удалить
err = os.RemoveAll(fullPath)
if err != nil {
// Если не получилось удалить, пробуем через sudo
slog.Warn(gotext.Get("Unable to remove cache item (%s) as current user, trying with sudo", entry))
sudoCmd := execWithPrivileges("rm", "-rf", fullPath)
if sudoErr := sudoCmd.Run(); sudoErr != nil {
// Если и через sudo не получилось, пропускаем с предупреждением
slog.Error(gotext.Get("Unable to remove cache item (%s)", entry), "error", err)
continue
}
}
}
}
// Очищаем временные директории
slog.Info(gotext.Get("Clearing temporary directory"))
tmpDir := "/tmp/alr"
if _, err := os.Stat(tmpDir); err == nil {
// Директория существует, пробуем очистить
err = os.RemoveAll(tmpDir)
if err != nil {
// Если не получилось удалить, пробуем через sudo
slog.Warn(gotext.Get("Unable to remove temporary directory as current user, trying with sudo"))
sudoCmd := execWithPrivileges("rm", "-rf", tmpDir)
if sudoErr := sudoCmd.Run(); sudoErr != nil {
slog.Error(gotext.Get("Unable to remove temporary directory"), "error", err)
}
}
}
// Создаем базовый каталог /tmp/alr с владельцем root:wheel и правами 2775
err = utils.EnsureTempDirWithRootOwner(tmpDir, 0o2775)
if err != nil {
slog.Warn(gotext.Get("Unable to create temporary directory"), "error", err)
}
// Создаем каталог dl с правами для группы wheel
dlDir := filepath.Join(tmpDir, "dl")
err = utils.EnsureTempDirWithRootOwner(dlDir, 0o2775)
if err != nil {
slog.Warn(gotext.Get("Unable to create download directory"), "error", err)
}
// Создаем каталог pkgs с правами для группы wheel
pkgsDir := filepath.Join(tmpDir, "pkgs")
err = utils.EnsureTempDirWithRootOwner(pkgsDir, 0o2775)
if err != nil {
slog.Warn(gotext.Get("Unable to create packages directory"), "error", err)
}
// Исправляем права на все существующие файлы в /tmp/alr, если там что-то есть
if _, err := os.Stat(tmpDir); err == nil {
slog.Info(gotext.Get("Fixing permissions on temporary files"))
// Проверяем, есть ли файлы в директории
entries, err := os.ReadDir(tmpDir)
if err == nil && len(entries) > 0 {
group := utils.GetPrivilegedGroup()
fixCmd := execWithPrivileges("chown", "-R", "root:"+group, tmpDir)
if fixErr := fixCmd.Run(); fixErr != nil {
slog.Warn(gotext.Get("Unable to fix file ownership"), "error", fixErr)
}
fixCmd = execWithPrivileges("chmod", "-R", "2775", tmpDir)
if fixErr := fixCmd.Run(); fixErr != nil {
slog.Warn(gotext.Get("Unable to fix file permissions"), "error", fixErr)
}
}
}
slog.Info(gotext.Get("Rebuilding cache"))
// Создаем директорию кэша с правильными правами
slog.Info(gotext.Get("Creating cache directory"))
err = utils.EnsureTempDirWithRootOwner(paths.CacheDir, 0o2775)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err)
}
deps, err = appbuilder.
New(ctx).
WithConfig().
WithDB().
WithReposForcePull().
Build()
if err != nil {
return cli.Exit(err, 1)
}
defer deps.Defer()
slog.Info(gotext.Get("Done"))
return nil
},
}
}
func makeWritableRecursive(path string) error {
return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
if err != nil { if err != nil {
return err log.Fatal("Unable to remove cache directory").Err(err).Send()
} }
info, err := d.Info() log.Info("Rebuilding cache").Send()
err = os.MkdirAll(paths.CacheDir, 0o755)
if err != nil { if err != nil {
return err log.Fatal("Unable to create new cache directory").Err(err).Send()
} }
newMode := info.Mode() | 0o200 err = repos.Pull(ctx, config.Config(ctx).Repos)
if d.IsDir() { if err != nil {
newMode |= 0o100 log.Fatal("Error pulling repos").Err(err).Send()
} }
return os.Chmod(path, newMode) log.Info("Done").Send()
})
return nil
},
} }

115
gen.go
View File

@@ -1,89 +1,46 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// 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 main package main
import ( import (
"os" "os"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/gen" "plemya-x.ru/alr/pkg/gen"
) )
func GenCmd() *cli.Command { var genCmd = &cli.Command{
return &cli.Command{ Name: "generate",
Name: "generate", Usage: "Generate a ALR script from a template",
Usage: gotext.Get("Generate a ALR script from a template"), Aliases: []string{"gen"},
Aliases: []string{"gen"}, Subcommands: []*cli.Command{
Subcommands: []*cli.Command{ genPipCmd,
{ },
Name: "pip", }
Usage: gotext.Get("Generate a ALR script for a pip module"),
Flags: []cli.Flag{ var genPipCmd = &cli.Command{
&cli.StringFlag{ Name: "pip",
Name: "name", Usage: "Generate a ALR script for a pip module",
Aliases: []string{"n"}, Flags: []cli.Flag{
Required: true, &cli.StringFlag{
}, Name: "name",
&cli.StringFlag{ Aliases: []string{"n"},
Name: "version", Required: true,
Aliases: []string{"v"}, },
Required: true, &cli.StringFlag{
}, Name: "version",
&cli.StringFlag{ Aliases: []string{"v"},
Name: "description", Required: true,
Aliases: []string{"d"}, },
}, &cli.StringFlag{
}, Name: "description",
Action: func(c *cli.Context) error { Aliases: []string{"d"},
return gen.Pip(os.Stdout, gen.PipOptions{ },
Name: c.String("name"), },
Version: c.String("version"), Action: func(c *cli.Context) error {
Description: c.String("description"), return gen.Pip(os.Stdout, gen.PipOptions{
}) Name: c.String("name"),
}, Version: c.String("version"),
}, Description: c.String("description"),
{ })
Name: "aur", },
Usage: gotext.Get("Generate a ALR script for an AUR package"),
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Required: true,
Usage: gotext.Get("Name of the AUR package"),
},
&cli.StringFlag{
Name: "version",
Aliases: []string{"v"},
Usage: gotext.Get("Version of the package (optional, uses latest if not specified)"),
},
},
Action: func(c *cli.Context) error {
return gen.AUR(os.Stdout, gen.AUROptions{
Name: c.String("name"),
Version: c.String("version"),
})
},
},
},
}
} }

View File

@@ -1,251 +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 main
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"log"
"os"
"reflect"
"strings"
"text/template"
)
func resolvedStructGenerator(buf *bytes.Buffer, fields []*ast.Field) {
contentTemplate := template.Must(template.New("").Parse(`
type {{ .EntityNameLower }}Resolved struct {
{{ .StructFields }}
}
`))
var structFieldsBuilder strings.Builder
for _, field := range fields {
for _, name := range field.Names {
// Поле с типом
fieldTypeStr := exprToString(field.Type)
// Структура поля
var buf bytes.Buffer
buf.WriteString("\t")
buf.WriteString(name.Name)
buf.WriteString(" ")
buf.WriteString(fieldTypeStr)
// Обработка json-тега
jsonTag := ""
if field.Tag != nil {
raw := strings.Trim(field.Tag.Value, "`")
tag := reflect.StructTag(raw)
if val := tag.Get("json"); val != "" {
jsonTag = val
}
}
if jsonTag == "" {
jsonTag = strings.ToLower(name.Name)
}
buf.WriteString(fmt.Sprintf(" `json:\"%s\"`", jsonTag))
buf.WriteString("\n")
structFieldsBuilder.Write(buf.Bytes())
}
}
params := struct {
EntityNameLower string
StructFields string
}{
EntityNameLower: "package",
StructFields: structFieldsBuilder.String(),
}
err := contentTemplate.Execute(buf, params)
if err != nil {
log.Fatalf("execute template: %v", err)
}
}
func toResolvedFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) {
contentTemplate := template.Must(template.New("").Parse(`
func {{ .EntityName }}ToResolved(src *{{ .EntityName }}) {{ .EntityNameLower }}Resolved {
return {{ .EntityNameLower }}Resolved{
{{ .Assignments }}
}
}
`))
var assignmentsBuilder strings.Builder
for _, field := range fields {
for _, name := range field.Names {
var assignBuf bytes.Buffer
assignBuf.WriteString("\t\t")
assignBuf.WriteString(name.Name)
assignBuf.WriteString(": ")
if isOverridableField(field.Type) {
assignBuf.WriteString(fmt.Sprintf("src.%s.Resolved()", name.Name))
} else {
assignBuf.WriteString(fmt.Sprintf("src.%s", name.Name))
}
assignBuf.WriteString(",\n")
assignmentsBuilder.Write(assignBuf.Bytes())
}
}
params := struct {
EntityName string
EntityNameLower string
Assignments string
}{
EntityName: "Package",
EntityNameLower: "package",
Assignments: assignmentsBuilder.String(),
}
err := contentTemplate.Execute(buf, params)
if err != nil {
log.Fatalf("execute template: %v", err)
}
}
func resolveFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) {
contentTemplate := template.Must(template.New("").Parse(`
func Resolve{{ .EntityName }}(pkg *{{ .EntityName }}, overrides []string) {
{{.Code}}}
`))
var codeBuilder strings.Builder
for _, field := range fields {
for _, name := range field.Names {
if isOverridableField(field.Type) {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("\t\tpkg.%s.Resolve(overrides)\n", name.Name))
codeBuilder.Write(buf.Bytes())
}
}
}
params := struct {
EntityName string
Code string
}{
EntityName: "Package",
Code: codeBuilder.String(),
}
err := contentTemplate.Execute(buf, params)
if err != nil {
log.Fatalf("execute template: %v", err)
}
}
func main() {
path := os.Getenv("GOFILE")
if path == "" {
log.Fatal("GOFILE must be set")
}
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
if err != nil {
log.Fatalf("parsing file: %v", err)
}
entityName := "Package" // имя структуры, которую анализируем
found := false
fields := make([]*ast.Field, 0)
// Ищем структуру с нужным именем
for _, decl := range node.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok || genDecl.Tok != token.TYPE {
continue
}
for _, spec := range genDecl.Specs {
typeSpec := spec.(*ast.TypeSpec)
if typeSpec.Name.Name != entityName {
continue
}
structType, ok := typeSpec.Type.(*ast.StructType)
if !ok {
continue
}
fields = structType.Fields.List
found = true
}
}
if !found {
log.Fatalf("struct %s not found", entityName)
}
var buf bytes.Buffer
buf.WriteString("// DO NOT EDIT MANUALLY. This file is generated.\n")
buf.WriteString("package alrsh")
resolvedStructGenerator(&buf, fields)
toResolvedFuncGenerator(&buf, fields)
resolveFuncGenerator(&buf, fields)
// Форматируем вывод
formatted, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("formatting: %v", err)
}
outPath := strings.TrimSuffix(path, ".go") + "_gen.go"
outFile, err := os.Create(outPath)
if err != nil {
log.Fatalf("create file: %v", err)
}
_, err = outFile.Write(formatted)
if err != nil {
log.Fatalf("writing output: %v", err)
}
outFile.Close()
}
func exprToString(expr ast.Expr) string {
if t, ok := expr.(*ast.IndexExpr); ok {
if ident, ok := t.X.(*ast.Ident); ok && ident.Name == "OverridableField" {
return exprToString(t.Index) // T
}
}
var buf bytes.Buffer
if err := format.Node(&buf, token.NewFileSet(), expr); err != nil {
return "<invalid>"
}
return buf.String()
}
func isOverridableField(expr ast.Expr) bool {
indexExpr, ok := expr.(*ast.IndexExpr)
if !ok {
return false
}
ident, ok := indexExpr.X.(*ast.Ident)
return ok && ident.Name == "OverridableField"
}

View File

@@ -1,416 +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 main
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"log"
"os"
"strings"
"text/template"
"unicode"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
type MethodInfo struct {
Name string
Params []ParamInfo
Results []ResultInfo
EntityName string
}
type ParamInfo struct {
Name string
Type string
}
type ResultInfo struct {
Name string
Type string
Index int
}
func extractImports(node *ast.File) []string {
var imports []string
for _, imp := range node.Imports {
if imp.Path.Value != "" {
imports = append(imports, imp.Path.Value)
}
}
return imports
}
func output(path string, buf bytes.Buffer) {
formatted, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("formatting: %v", err)
}
outPath := strings.TrimSuffix(path, ".go") + "_gen.go"
outFile, err := os.Create(outPath)
if err != nil {
log.Fatalf("create file: %v", err)
}
_, err = outFile.Write(formatted)
if err != nil {
log.Fatalf("writing output: %v", err)
}
outFile.Close()
}
func main() {
path := os.Getenv("GOFILE")
if path == "" {
log.Fatal("GOFILE must be set")
}
if len(os.Args) < 2 {
log.Fatal("At least one entity name must be provided")
}
entityNames := os.Args[1:]
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
if err != nil {
log.Fatalf("parsing file: %v", err)
}
packageName := node.Name.Name
// Find all specified entities
entityData := make(map[string][]*ast.Field)
for _, decl := range node.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok || genDecl.Tok != token.TYPE {
continue
}
for _, spec := range genDecl.Specs {
typeSpec := spec.(*ast.TypeSpec)
for _, entityName := range entityNames {
if typeSpec.Name.Name == entityName {
interfaceType, ok := typeSpec.Type.(*ast.InterfaceType)
if !ok {
log.Fatalf("entity %s is not an interface", entityName)
}
entityData[entityName] = interfaceType.Methods.List
}
}
}
}
// Verify all entities were found
for _, entityName := range entityNames {
if _, found := entityData[entityName]; !found {
log.Fatalf("interface %s not found", entityName)
}
}
var buf bytes.Buffer
buf.WriteString(`
// DO NOT EDIT MANUALLY. This file is generated.
// 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/>.
`)
buf.WriteString(fmt.Sprintf("package %s\n", packageName))
// Generate base structures for all entities
baseStructs(&buf, entityNames, extractImports(node))
// Generate method-specific code for each entity
for _, entityName := range entityNames {
methods := parseMethodsFromFields(entityName, entityData[entityName])
argsGen(&buf, methods)
}
output(path, buf)
}
func parseMethodsFromFields(entityName string, fields []*ast.Field) []MethodInfo {
var methods []MethodInfo
for _, field := range fields {
if len(field.Names) == 0 {
continue
}
methodName := field.Names[0].Name
funcType, ok := field.Type.(*ast.FuncType)
if !ok {
continue
}
method := MethodInfo{
Name: methodName,
EntityName: entityName,
}
// Parse parameters, excluding context.Context
if funcType.Params != nil {
for i, param := range funcType.Params.List {
paramType := typeToString(param.Type)
// Skip context.Context parameters
if paramType == "context.Context" {
continue
}
if len(param.Names) == 0 {
method.Params = append(method.Params, ParamInfo{
Name: fmt.Sprintf("Arg%d", i),
Type: paramType,
})
} else {
for _, name := range param.Names {
method.Params = append(method.Params, ParamInfo{
Name: cases.Title(language.Und, cases.NoLower).String(name.Name),
Type: paramType,
})
}
}
}
}
// Parse results
if funcType.Results != nil {
resultIndex := 0
for _, result := range funcType.Results.List {
resultType := typeToString(result.Type)
if resultType == "error" {
continue // Skip error in response struct
}
if len(result.Names) == 0 {
method.Results = append(method.Results, ResultInfo{
Name: fmt.Sprintf("Result%d", resultIndex),
Type: resultType,
Index: resultIndex,
})
} else {
for _, name := range result.Names {
method.Results = append(method.Results, ResultInfo{
Name: cases.Title(language.Und, cases.NoLower).String(name.Name),
Type: resultType,
Index: resultIndex,
})
}
}
resultIndex++
}
}
methods = append(methods, method)
}
return methods
}
func argsGen(buf *bytes.Buffer, methods []MethodInfo) {
// Add template functions first
funcMap := template.FuncMap{
"lowerFirst": func(s string) string {
if len(s) == 0 {
return s
}
return strings.ToLower(s[:1]) + s[1:]
},
"zeroValue": func(typeName string) string {
typeName = strings.TrimSpace(typeName)
switch typeName {
case "string":
return "\"\""
case "int", "int8", "int16", "int32", "int64":
return "0"
case "uint", "uint8", "uint16", "uint32", "uint64":
return "0"
case "float32", "float64":
return "0.0"
case "bool":
return "false"
}
if strings.HasPrefix(typeName, "*") {
return "nil"
}
if strings.HasPrefix(typeName, "[]") ||
strings.HasPrefix(typeName, "map[") ||
strings.HasPrefix(typeName, "chan ") {
return "nil"
}
if typeName == "interface{}" {
return "nil"
}
// If external type: pkg.Type
if strings.Contains(typeName, ".") {
return typeName + "{}"
}
// If starts with uppercase — likely struct
if len(typeName) > 0 && unicode.IsUpper(rune(typeName[0])) {
return typeName + "{}"
}
return "nil"
},
}
argsTemplate := template.Must(template.New("args").Funcs(funcMap).Parse(`
{{range .}}
type {{.EntityName}}{{.Name}}Args struct {
{{range .Params}} {{.Name}} {{.Type}}
{{end}}}
type {{.EntityName}}{{.Name}}Resp struct {
{{range .Results}} {{.Name}} {{.Type}}
{{end}}}
func (s *{{.EntityName}}RPC) {{.Name}}(ctx context.Context, {{range $i, $p := .Params}}{{if $i}}, {{end}}{{lowerFirst $p.Name}} {{$p.Type}}{{end}}) ({{range $i, $r := .Results}}{{if $i}}, {{end}}{{$r.Type}}{{end}}{{if .Results}}, {{end}}error) {
var resp *{{.EntityName}}{{.Name}}Resp
err := s.client.Call("Plugin.{{.Name}}", &{{.EntityName}}{{.Name}}Args{
{{range .Params}} {{.Name}}: {{lowerFirst .Name}},
{{end}} }, &resp)
if err != nil {
return {{range $i, $r := .Results}}{{if $i}}, {{end}}{{zeroValue $r.Type}}{{end}}{{if .Results}}, {{end}}err
}
return {{range $i, $r := .Results}}{{if $i}}, {{end}}resp.{{$r.Name}}{{end}}{{if .Results}}, {{end}}nil
}
func (s *{{.EntityName}}RPCServer) {{.Name}}(args *{{.EntityName}}{{.Name}}Args, resp *{{.EntityName}}{{.Name}}Resp) error {
{{if .Results}}{{range $i, $r := .Results}}{{if $i}}, {{end}}{{lowerFirst $r.Name}}{{end}}, err := {{else}}err := {{end}}s.Impl.{{.Name}}(context.Background(),{{range $i, $p := .Params}}{{if $i}}, {{end}}args.{{$p.Name}}{{end}})
if err != nil {
return err
}
{{if .Results}}*resp = {{.EntityName}}{{.Name}}Resp{
{{range .Results}} {{.Name}}: {{lowerFirst .Name}},
{{end}} }
{{else}}*resp = {{.EntityName}}{{.Name}}Resp{}
{{end}}return nil
}
{{end}}
`))
err := argsTemplate.Execute(buf, methods)
if err != nil {
log.Fatalf("execute args template: %v", err)
}
}
func typeToString(expr ast.Expr) string {
switch t := expr.(type) {
case *ast.Ident:
return t.Name
case *ast.StarExpr:
return "*" + typeToString(t.X)
case *ast.ArrayType:
return "[]" + typeToString(t.Elt)
case *ast.SelectorExpr:
xStr := typeToString(t.X)
if xStr == "context" && t.Sel.Name == "Context" {
return "context.Context"
}
return xStr + "." + t.Sel.Name
case *ast.InterfaceType:
return "interface{}"
default:
return "interface{}"
}
}
func baseStructs(buf *bytes.Buffer, entityNames, imports []string) {
// Ensure "context" is included in imports
updatedImports := imports
hasContext := false
for _, imp := range imports {
if strings.Contains(imp, `"context"`) {
hasContext = true
break
}
}
if !hasContext {
updatedImports = append(updatedImports, `"context"`)
}
contentTemplate := template.Must(template.New("").Parse(`
import (
"net/rpc"
"github.com/hashicorp/go-plugin"
{{range .Imports}} {{.}}
{{end}}
)
{{range .EntityNames}}
type {{ . }}Plugin struct {
Impl {{ . }}
}
type {{ . }}RPCServer struct {
Impl {{ . }}
}
type {{ . }}RPC struct {
client *rpc.Client
}
func (p *{{ . }}Plugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &{{ . }}RPC{client: c}, nil
}
func (p *{{ . }}Plugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &{{ . }}RPCServer{Impl: p.Impl}, nil
}
{{end}}
`))
err := contentTemplate.Execute(buf, struct {
EntityNames []string
Imports []string
}{
EntityNames: entityNames,
Imports: updatedImports,
})
if err != nil {
log.Fatalf("execute template: %v", err)
}
}

126
go.mod
View File

@@ -1,49 +1,39 @@
module gitea.plemya-x.ru/Plemya-x/ALR module plemya-x.ru/alr
go 1.24.4 go 1.21
toolchain go1.21.3
require ( require (
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/chroma/v2 v2.9.1 github.com/alecthomas/chroma/v2 v2.9.1
github.com/bmatcuk/doublestar/v4 v4.8.1 github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbles v0.20.0 github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/bubbletea v1.2.4 github.com/charmbracelet/lipgloss v0.8.0
github.com/charmbracelet/lipgloss v1.0.0 github.com/go-git/go-billy/v5 v5.5.0
github.com/charmbracelet/log v0.4.0 github.com/go-git/go-git/v5 v5.12.0
github.com/go-git/go-billy/v5 v5.6.0
github.com/go-git/go-git/v5 v5.13.0
github.com/goccy/go-yaml v1.18.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/goreleaser/nfpm/v2 v2.41.0 github.com/goreleaser/nfpm/v2 v2.41.0
github.com/hashicorp/go-hclog v0.14.1 github.com/jmoiron/sqlx v1.3.5
github.com/hashicorp/go-plugin v1.6.3 github.com/mattn/go-isatty v0.0.19
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
github.com/knadh/koanf/parsers/toml/v2 v2.2.0
github.com/knadh/koanf/providers/confmap v1.0.0
github.com/knadh/koanf/providers/env v1.1.0
github.com/knadh/koanf/providers/file v1.2.0
github.com/knadh/koanf/v2 v2.2.1
github.com/leonelquinteros/gotext v1.7.0
github.com/mattn/go-isatty v0.0.20
github.com/mholt/archiver/v4 v4.0.0-alpha.8 github.com/mholt/archiver/v4 v4.0.0-alpha.8
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/muesli/reflow v0.3.0 github.com/muesli/reflow v0.3.0
github.com/pelletier/go-toml/v2 v2.2.4 github.com/pelletier/go-toml/v2 v2.1.0
github.com/stretchr/testify v1.10.0 github.com/schollz/progressbar/v3 v3.13.1
github.com/urfave/cli/v2 v2.25.7 github.com/urfave/cli/v2 v2.25.7
github.com/vmihailenco/msgpack/v5 v5.3.5 github.com/vmihailenco/msgpack/v5 v5.3.5
go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 go.elara.ws/logger v0.0.0-20230421022458-e80700db2090
go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9 go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.23.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
golang.org/x/sys v0.33.0 golang.org/x/sys v0.20.0
golang.org/x/text v0.23.0 golang.org/x/text v0.15.0
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.7.0
xorm.io/xorm v1.3.9 plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283
) )
require ( require (
@@ -53,7 +43,7 @@ require (
github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect
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.0.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
@@ -61,99 +51,68 @@ require (
github.com/bodgit/sevenzip v1.3.0 // indirect github.com/bodgit/sevenzip v1.3.0 // indirect
github.com/bodgit/windows v1.0.0 // indirect github.com/bodgit/windows v1.0.0 // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/cloudflare/circl v1.3.8 // indirect
github.com/charmbracelet/x/ansi v0.4.5 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/connesc/cipherio v0.2.1 // indirect github.com/connesc/cipherio v0.2.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/creack/pty v1.1.24 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect github.com/dsnet/compress v0.0.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gkampitakis/ciinfo v0.3.2 // indirect
github.com/gkampitakis/go-diff v1.3.2 // indirect
github.com/gkampitakis/go-snaps v0.5.13 // indirect
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-viper/mapstructure/v2 v2.3.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/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.4.0 // indirect
github.com/gookit/color v1.5.1 // indirect
github.com/goreleaser/chglog v0.6.1 // indirect github.com/goreleaser/chglog v0.6.1 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect github.com/goreleaser/fileglob v1.3.0 // indirect
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/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
github.com/klauspost/pgzip v1.2.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/maruel/natural v1.1.1 // indirect github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // 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/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/rogpeppe/go-internal v1.13.1 // 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.3.1 // indirect github.com/shopspring/decimal v1.2.0 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect github.com/skeema/knownhosts v1.2.2 // indirect
github.com/spf13/cast v1.7.1 // 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/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // 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
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/mod v0.19.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.38.0 // indirect golang.org/x/net v0.23.0 // indirect
golang.org/x/sync v0.12.0 // indirect golang.org/x/sync v0.5.0 // indirect
golang.org/x/term v0.30.0 // indirect golang.org/x/term v0.20.0 // indirect
golang.org/x/tools v0.23.0 // indirect golang.org/x/tools v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
google.golang.org/grpc v1.67.3 // indirect
google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect modernc.org/ccgo/v3 v3.16.13 // indirect
@@ -163,5 +122,4 @@ 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
) )

317
go.sum
View File

@@ -17,10 +17,6 @@ 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/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=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
@@ -41,8 +37,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s= github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
@@ -65,51 +61,42 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8= github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8=
github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY= github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY=
github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM= github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM=
github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA= github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM=
github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -120,53 +107,30 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 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.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
github.com/gkampitakis/go-snaps v0.5.13 h1:Hhjmvv1WboSCxkR9iU2mj5PQ8tsz/y8ECGrIbjjPF8Q=
github.com/gkampitakis/go-snaps v0.5.13/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 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/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
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/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
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=
@@ -183,10 +147,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 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=
@@ -195,10 +155,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -208,13 +166,13 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S3
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE= github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/goreleaser/chglog v0.6.1 h1:NZKiX8l0FTQPRzBgKST7knvNZmZ04f7PEGkN2wInfhE= github.com/goreleaser/chglog v0.6.1 h1:NZKiX8l0FTQPRzBgKST7knvNZmZ04f7PEGkN2wInfhE=
@@ -225,22 +183,14 @@ github.com/goreleaser/nfpm/v2 v2.41.0 h1:JyMzS/EwqaWbFs+7Z9oZ4Hkk4or00gUTqwm9Dgr
github.com/goreleaser/nfpm/v2 v2.41.0/go.mod h1:VPc5kF5OgfA+BosV/A2aB+Vg34honjWvp0Vt8ogsSi0= github.com/goreleaser/nfpm/v2 v2.41.0/go.mod h1:VPc5kF5OgfA+BosV/A2aB+Vg34honjWvp0Vt8ogsSi0=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg=
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
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=
@@ -249,16 +199,13 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
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=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
@@ -270,18 +217,6 @@ github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/toml/v2 v2.2.0 h1:2nV7tHYJ5OZy2BynQ4mOJ6k5bDqbbCzRERLUKBytz3A=
github.com/knadh/koanf/parsers/toml/v2 v2.2.0/go.mod h1:JpjTeK1Ge1hVX0wbof5DMCuDBriR8bWgeQP98eeOZpI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWyJgef9oQMSc=
github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY=
github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U=
github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
github.com/knadh/koanf/v2 v2.2.1 h1:jaleChtw85y3UdBnI0wCqcg1sj1gPoz6D3caGNHtrNE=
github.com/knadh/koanf/v2 v2.2.1/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -289,34 +224,33 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 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/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
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/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A= github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@@ -325,13 +259,8 @@ 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/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
@@ -340,21 +269,14 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk= github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
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/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -365,55 +287,48 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
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/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
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.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
@@ -427,17 +342,17 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
go.alt-gnome.ru/capytest v0.0.2 h1:clmvIqmYS86hhA1rsvivSSPpfOFkJTpbn38EQP7I3E8= go.elara.ws/logger v0.0.0-20230421022458-e80700db2090 h1:RVC8XvWo6Yw4HUshqx4TSzuBDScDghafU6QFRJ4xPZg=
go.alt-gnome.ru/capytest v0.0.2/go.mod h1:lvxPx3H6h+LPnStBFblgoT2wkjv0wbug3S14troykEg= go.elara.ws/logger v0.0.0-20230421022458-e80700db2090/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM=
go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 h1:NST+V5LV/eLgs0p6PsuvfHiZ4UrIWqftCdifO8zgg0g= go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6 h1:4xCBxLPBn3Y2DuIcj8zQ1tQOFLrpu6tEIGUWn/Q6zPM=
go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:qiM8LARP+JBZr5mrDoVylOoqjrN0MAzvZ21NR9qMc0Y= go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6/go.mod h1:NmfCFqwq7X/aqa/ZVkIysj17JyMEY4Bb5E921kMswNo=
go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9 h1:VZclgdJxARvhZ6PIWWW2hQ6Ge4XeE36pzUr/U/y62bE=
go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:Wpq1Ny3eMzADJpMJArA2TZGZbsviUBmawtEPcxnoerg=
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY= go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY=
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk= go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -453,8 +368,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -463,8 +380,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -483,11 +400,11 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.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=
@@ -504,8 +421,10 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -518,10 +437,10 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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=
@@ -531,7 +450,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -539,22 +457,27 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -563,8 +486,10 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -592,8 +517,9 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -623,8 +549,6 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -632,25 +556,14 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8=
google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@@ -690,12 +603,10 @@ modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4= mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY= mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283 h1:BXCLPeA8g2M6qYngicyxyB/2Bo4J54Q9Rb+8jMmE3ik=
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283/go.mod h1:itzL9Jx52VXOhRaucFHuMpa3y7iwjnuLGdNvypoh/S4=
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=

155
helper.go
View File

@@ -1,110 +1,87 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// 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 main package main
import ( import (
"fmt" "fmt"
"log/slog"
"os" "os"
"strings" "strings"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "plemya-x.ru/alr/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" "plemya-x.ru/alr/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" "plemya-x.ru/alr/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" "plemya-x.ru/alr/pkg/loggerctx"
) )
func HelperCmd() *cli.Command { var helperCmd = &cli.Command{
helperListCmd := &cli.Command{ Name: "helper",
Name: "list", Usage: "Run a ALR helper command",
Usage: gotext.Get("List all the available helper commands"), ArgsUsage: `<helper_name|"list">`,
Aliases: []string{"ls"}, Subcommands: []*cli.Command{helperListCmd},
Action: func(ctx *cli.Context) error { Flags: []cli.Flag{
for name := range helpers.Helpers { &cli.StringFlag{
fmt.Println(name) Name: "dest-dir",
} Aliases: []string{"d"},
return nil Usage: "The directory that the install commands will install to",
Value: "dest",
}, },
} },
Action: func(c *cli.Context) error {
ctx := c.Context
log := loggerctx.From(ctx)
return &cli.Command{ if c.Args().Len() < 1 {
Name: "helper", cli.ShowSubcommandHelpAndExit(c, 1)
Usage: gotext.Get("Run a ALR helper command"), }
ArgsUsage: `<helper_name|"list">`,
Subcommands: []*cli.Command{helperListCmd},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "dest-dir",
Aliases: []string{"d"},
Usage: gotext.Get("The directory that the install commands will install to"),
Value: "dest",
},
},
Action: func(c *cli.Context) error {
ctx := c.Context
if c.Args().Len() < 1 { helper, ok := helpers.Helpers[c.Args().First()]
cli.ShowSubcommandHelpAndExit(c, 1) if !ok {
} log.Fatal("No such helper command").Str("name", c.Args().First()).Send()
}
helper, ok := helpers.Helpers[c.Args().First()] wd, err := os.Getwd()
if !ok { if err != nil {
slog.Error(gotext.Get("No such helper command"), "name", c.Args().First()) log.Fatal("Error getting working directory").Err(err).Send()
return cli.Exit(gotext.Get("No such helper command"), 1) }
}
wd, err := os.Getwd() info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) log.Fatal("Error getting working directory").Err(err).Send()
} }
info, err := distro.ParseOSRelease(ctx) hc := interp.HandlerContext{
if err != nil { Env: expand.ListEnviron(
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) "pkgdir="+c.String("dest-dir"),
} "DISTRO_ID="+info.ID,
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
"ARCH="+cpu.Arch(),
),
Dir: wd,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
hc := interp.HandlerContext{ return helper(hc, c.Args().First(), c.Args().Slice()[1:])
Env: expand.ListEnviron( },
"pkgdir="+c.String("dest-dir"), CustomHelpTemplate: cli.CommandHelpTemplate,
"DISTRO_ID="+info.ID, BashComplete: func(ctx *cli.Context) {
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "), for name := range helpers.Helpers {
"ARCH="+cpu.Arch(), fmt.Println(name)
), }
Dir: wd, },
Stdin: os.Stdin, }
Stdout: os.Stdout,
Stderr: os.Stderr, var helperListCmd = &cli.Command{
} Name: "list",
Usage: "List all the available helper commands",
return helper(hc, c.Args().First(), c.Args().Slice()[1:]) Aliases: []string{"ls"},
}, Action: func(ctx *cli.Context) error {
CustomHelpTemplate: cli.CommandHelpTemplate, for name := range helpers.Helpers {
BashComplete: func(ctx *cli.Context) { fmt.Println(name)
for name := range helpers.Helpers { }
fmt.Println(name) return nil
} },
},
}
} }

184
info.go
View File

@@ -1,21 +1,20 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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 main package main
@@ -23,123 +22,86 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/goccy/go-yaml"
"github.com/jeandeaual/go-locale"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gopkg.in/yaml.v3"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "plemya-x.ru/alr/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" "plemya-x.ru/alr/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" "plemya-x.ru/alr/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" "plemya-x.ru/alr/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" "plemya-x.ru/alr/pkg/loggerctx"
"plemya-x.ru/alr/pkg/repos"
) )
func InfoCmd() *cli.Command { var infoCmd = &cli.Command{
return &cli.Command{ Name: "info",
Name: "info", Usage: "Print information about a package",
Usage: gotext.Get("Print information about a package"), Flags: []cli.Flag{
Flags: []cli.Flag{ &cli.BoolFlag{
&cli.BoolFlag{ Name: "all",
Name: "all", Aliases: []string{"a"},
Aliases: []string{"a"}, Usage: "Show all information, not just for the current distro",
Usage: gotext.Get("Show all information, not just for the current distro"),
},
}, },
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { },
Action: func(c *cli.Context) error {
ctx := c.Context
log := loggerctx.From(ctx)
ctx := c.Context args := c.Args()
deps, err := appbuilder. if args.Len() < 1 {
New(ctx). log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send()
WithConfig(). }
WithDB().
Build()
if err != nil {
return err
}
defer deps.Defer()
result, err := deps.DB.GetPkgs(c.Context, "true") err := repos.Pull(ctx, config.Config(ctx).Repos)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) log.Fatal("Error pulling repositories").Err(err).Send()
} }
for _, pkg := range result { found, _, err := repos.FindPkgs(ctx, args.Slice())
fmt.Println(pkg.Name) if err != nil {
} log.Fatal("Error finding packages").Err(err).Send()
return nil }
}),
Action: func(c *cli.Context) error {
// Запуск от текущего пользователя
args := c.Args() if len(found) == 0 {
if args.Len() < 1 { os.Exit(1)
return cli.Exit(gotext.Get("Command info expected at least 1 argument, got %d", args.Len()), 1) }
}
ctx := c.Context pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive"))
deps, err := appbuilder. var names []string
New(ctx). all := c.Bool("all")
WithConfig().
WithDB().
WithDistroInfo().
WithRepos().
Build()
if err != nil {
return cli.Exit(err, 1)
}
defer deps.Defer()
rs := deps.Repos
found, _, err := rs.FindPkgs(ctx, args.Slice())
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error finding packages"), err)
}
if len(found) == 0 {
return cliutils.FormatCliExit(gotext.Get("Package not found"), err)
}
pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive"))
var names []string
all := c.Bool("all")
systemLang, err := locale.GetLanguage()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Can't detect system language"), err)
}
if systemLang == "" {
systemLang = "en"
}
if !all {
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) log.Fatal("Error parsing os-release file").Err(err).Send()
} }
names, err = overrides.Resolve( names, err = overrides.Resolve(
info, info,
overrides.DefaultOpts. overrides.DefaultOpts.
WithLanguages([]string{systemLang}), WithLanguages([]string{config.SystemLang()}),
) )
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err) log.Fatal("Error resolving overrides").Err(err).Send()
} }
}
for _, pkg := range pkgs { for _, pkg := range pkgs {
alrsh.ResolvePackage(&pkg, names) if !all {
view := alrsh.NewPackageView(pkg) err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
view.Resolved = !all
err = yaml.NewEncoder(os.Stdout, yaml.UseJSONMarshaler(), yaml.OmitEmpty()).Encode(view)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) log.Fatal("Error encoding script variables").Err(err).Send()
}
} else {
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
if err != nil {
log.Fatal("Error encoding script variables").Err(err).Send()
} }
fmt.Println("---")
} }
return nil fmt.Println("---")
}, }
}
return nil
},
} }

View File

@@ -1,211 +1,123 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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 main package main
import ( import (
"fmt" "fmt"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" "plemya-x.ru/alr/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "plemya-x.ru/alr/internal/config"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" "plemya-x.ru/alr/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" "plemya-x.ru/alr/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" "plemya-x.ru/alr/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" "plemya-x.ru/alr/pkg/loggerctx"
"plemya-x.ru/alr/pkg/manager"
"plemya-x.ru/alr/pkg/repos"
) )
func InstallCmd() *cli.Command { var installCmd = &cli.Command{
return &cli.Command{ Name: "install",
Name: "install", Usage: "Install a new package",
Usage: gotext.Get("Install a new package"), Aliases: []string{"in"},
Aliases: []string{"in"}, Flags: []cli.Flag{
Flags: []cli.Flag{ &cli.BoolFlag{
&cli.BoolFlag{ Name: "clean",
Name: "clean", Aliases: []string{"c"},
Aliases: []string{"c"}, Usage: "Build package from scratch even if there's an already built package available",
Usage: gotext.Get("Build package from scratch even if there's an already built package available"),
},
}, },
Action: utils.RootNeededAction(func(c *cli.Context) error { },
args := c.Args() Action: func(c *cli.Context) error {
if args.Len() < 1 { ctx := c.Context
return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil) log := loggerctx.From(ctx)
}
args := c.Args()
if args.Len() < 1 {
log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send()
}
installer, installerClose, err := build.GetSafeInstaller() mgr := manager.Detect()
if mgr == nil {
log.Fatal("Unable to detect a supported package manager on the system").Send()
}
err := repos.Pull(ctx, config.Config(ctx).Repos)
if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send()
}
found, notFound, err := repos.FindPkgs(ctx, args.Slice())
if err != nil {
log.Fatal("Error finding packages").Err(err).Send()
}
pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive"))
build.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
})
return nil
},
BashComplete: func(c *cli.Context) {
log := loggerctx.From(c.Context)
result, err := db.GetPkgs(c.Context, "true")
if err != nil {
log.Fatal("Error getting packages").Err(err).Send()
}
defer result.Close()
for result.Next() {
var pkg db.Package
err = result.StructScan(&pkg)
if err != nil { if err != nil {
return err log.Fatal("Error iterating over packages").Err(err).Send()
}
defer installerClose()
scripter, scripterClose, err := build.GetSafeScriptExecutor()
if err != nil {
return err
}
defer scripterClose()
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithRepos().
WithDistroInfo().
WithManager().
Build()
if err != nil {
return err
}
defer deps.Defer()
builder, err := build.NewMainBuilder(
deps.Cfg,
deps.Manager,
deps.Repos,
scripter,
installer,
)
if err != nil {
return err
} }
_, err = builder.InstallPkgs( fmt.Println(pkg.Name)
ctx, }
&build.BuildArgs{ },
Opts: &types.BuildOpts{
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
},
Info: deps.Info,
PkgFormat_: build.GetPkgFormat(deps.Manager),
},
args.Slice(),
)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error when installing the package"), err)
}
return nil
}),
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
Build()
if err != nil {
return err
}
defer deps.Defer()
result, err := deps.DB.GetPkgs(c.Context, "true")
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
}
for _, pkg := range result {
fmt.Println(pkg.Name)
}
return nil
}),
}
} }
func RemoveCmd() *cli.Command { var removeCmd = &cli.Command{
return &cli.Command{ Name: "remove",
Name: "remove", Usage: "Remove an installed package",
Usage: gotext.Get("Remove an installed package"), Aliases: []string{"rm"},
Aliases: []string{"rm"}, Action: func(c *cli.Context) error {
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { log := loggerctx.From(c.Context)
ctx := c.Context
deps, err := appbuilder. args := c.Args()
New(ctx). if args.Len() < 1 {
WithConfig(). log.Fatalf("Command remove expected at least 1 argument, got %d", args.Len()).Send()
WithDB(). }
WithManager().
Build()
if err != nil {
return cli.Exit(err, 1)
}
defer deps.Defer()
installedAlrPackages := map[string]string{} mgr := manager.Detect()
installed, err := deps.Manager.ListInstalled(&manager.Opts{}) if mgr == nil {
if err != nil { log.Fatal("Unable to detect a supported package manager on the system").Send()
return cliutils.FormatCliExit(gotext.Get("Error listing installed packages"), err) }
}
for pkgName, version := range installed {
matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName)
if matches != nil {
packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")]
repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")]
installedAlrPackages[fmt.Sprintf("%s/%s", repoName, packageName)] = version
}
}
result, err := deps.DB.GetPkgs(c.Context, "true") err := mgr.Remove(nil, c.Args().Slice()...)
if err != nil { if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) log.Fatal("Error removing packages").Err(err).Send()
} }
for _, pkg := range result { return nil
_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] },
if !ok {
continue
}
fmt.Println(pkg.Name)
}
return nil
}),
Action: utils.RootNeededAction(func(c *cli.Context) error {
args := c.Args()
if args.Len() < 1 {
return cliutils.FormatCliExit(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len()), nil)
}
deps, err := appbuilder.
New(c.Context).
WithManager().
Build()
if err != nil {
return err
}
defer deps.Defer()
if err := deps.Manager.Remove(&manager.Opts{
NoConfirm: !c.Bool("interactive"),
}, c.Args().Slice()...); err != nil {
return cliutils.FormatCliExit(gotext.Get("Error removing packages"), err)
}
return nil
}),
}
} }

View File

@@ -1,156 +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 main
import (
"log/slog"
"os"
"syscall"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
)
func InternalBuildCmd() *cli.Command {
return &cli.Command{
Name: "_internal-safe-script-executor",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid())
cfg := config.New()
err := cfg.Load()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
}
logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Output: os.Stderr,
Level: hclog.Debug,
JSONFormat: false,
DisableTime: true,
})
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: build.HandshakeConfig,
Plugins: map[string]plugin.Plugin{
"script-executor": &build.ScriptExecutorPlugin{
Impl: build.NewLocalScriptExecutor(cfg),
},
},
Logger: logger,
})
return nil
},
}
}
func InternalReposCmd() *cli.Command {
return &cli.Command{
Name: "_internal-repos",
HideHelp: true,
Hidden: true,
Action: utils.RootNeededAction(func(ctx *cli.Context) error {
logger.SetupForGoPlugin()
deps, err := appbuilder.
New(ctx.Context).
WithConfig().
WithDB().
WithReposNoPull().
Build()
if err != nil {
return err
}
defer deps.Defer()
pluginCfg := build.GetPluginServeCommonConfig()
pluginCfg.Plugins = map[string]plugin.Plugin{
"repos": &build.ReposExecutorPlugin{
Impl: build.NewRepos(
deps.Repos,
),
},
}
plugin.Serve(pluginCfg)
return nil
}),
}
}
func InternalInstallCmd() *cli.Command {
return &cli.Command{
Name: "_internal-installer",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
// Запуск от текущего пользователя, повышение прав будет через sudo при необходимости
deps, err := appbuilder.
New(c.Context).
WithConfig().
WithDB().
WithReposNoPull().
Build()
if err != nil {
return err
}
defer deps.Defer()
logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Output: os.Stderr,
Level: hclog.Trace,
JSONFormat: true,
DisableTime: true,
})
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: build.HandshakeConfig,
Plugins: map[string]plugin.Plugin{
"installer": &build.InstallerExecutorPlugin{
Impl: build.NewInstaller(
manager.Detect(),
),
},
},
Logger: logger,
})
return nil
},
}
}

View File

@@ -1,745 +0,0 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// 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 (
"bytes"
"context"
"encoding/gob"
"errors"
"fmt"
"log/slog"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/stats"
"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"
)
type BuildInput struct {
opts *types.BuildOpts
info *distro.OSRelease
pkgFormat string
script string
repository string
packages []string
}
func (bi *BuildInput) GobEncode() ([]byte, error) {
w := new(bytes.Buffer)
encoder := gob.NewEncoder(w)
if err := encoder.Encode(bi.opts); err != nil {
return nil, err
}
if err := encoder.Encode(bi.info); err != nil {
return nil, err
}
if err := encoder.Encode(bi.pkgFormat); err != nil {
return nil, err
}
if err := encoder.Encode(bi.script); err != nil {
return nil, err
}
if err := encoder.Encode(bi.repository); err != nil {
return nil, err
}
if err := encoder.Encode(bi.packages); err != nil {
return nil, err
}
return w.Bytes(), nil
}
func (bi *BuildInput) GobDecode(data []byte) error {
r := bytes.NewBuffer(data)
decoder := gob.NewDecoder(r)
if err := decoder.Decode(&bi.opts); err != nil {
return err
}
if err := decoder.Decode(&bi.info); err != nil {
return err
}
if err := decoder.Decode(&bi.pkgFormat); err != nil {
return err
}
if err := decoder.Decode(&bi.script); err != nil {
return err
}
if err := decoder.Decode(&bi.repository); err != nil {
return err
}
if err := decoder.Decode(&bi.packages); err != nil {
return err
}
return nil
}
func (b *BuildInput) Repository() string {
return b.repository
}
func (b *BuildInput) BuildOpts() *types.BuildOpts {
return b.opts
}
func (b *BuildInput) OSRelease() *distro.OSRelease {
return b.info
}
func (b *BuildInput) PkgFormat() string {
return b.pkgFormat
}
type BuildOptsProvider interface {
BuildOpts() *types.BuildOpts
}
type OsInfoProvider interface {
OSRelease() *distro.OSRelease
}
type PkgFormatProvider interface {
PkgFormat() string
}
type RepositoryProvider interface {
Repository() string
}
// ================================================
type BuiltDep struct {
Name string
Path string
}
func Map[T, R any](items []T, f func(T) R) []R {
res := make([]R, len(items))
for i, item := range items {
res[i] = f(item)
}
return res
}
func GetBuiltPaths(deps []*BuiltDep) []string {
return Map(deps, func(dep *BuiltDep) string {
return dep.Path
})
}
func GetBuiltName(deps []*BuiltDep) []string {
return Map(deps, func(dep *BuiltDep) string {
return dep.Name
})
}
type PackageFinder interface {
FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error)
}
type Config interface {
GetPaths() *config.Paths
PagerStyle() string
}
type FunctionsOutput struct {
Contents *[]string
}
// EXECUTORS
type ScriptResolverExecutor interface {
ResolveScript(ctx context.Context, pkg *alrsh.Package) *ScriptInfo
}
type CacheExecutor interface {
CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *alrsh.Package) (string, bool, error)
}
type ScriptViewerExecutor interface {
ViewScript(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile, basePkg string) error
}
type CheckerExecutor interface {
PerformChecks(
ctx context.Context,
input *BuildInput,
vars *alrsh.Package,
) (bool, error)
}
type SourcesInput struct {
Sources []string
Checksums []string
}
type SourceDownloaderExecutor interface {
DownloadSources(
ctx context.Context,
input *BuildInput,
basePkg string,
si SourcesInput,
) error
}
//
func NewBuilder(
scriptResolver ScriptResolverExecutor,
scriptExecutor ScriptExecutor,
cacheExecutor CacheExecutor,
scriptViewerExecutor ScriptViewerExecutor,
checkerExecutor CheckerExecutor,
installerExecutor InstallerExecutor,
sourceExecutor SourceDownloaderExecutor,
) *Builder {
return &Builder{
scriptResolver: scriptResolver,
scriptExecutor: scriptExecutor,
cacheExecutor: cacheExecutor,
scriptViewerExecutor: scriptViewerExecutor,
checkerExecutor: checkerExecutor,
installerExecutor: installerExecutor,
sourceExecutor: sourceExecutor,
}
}
type Builder struct {
scriptResolver ScriptResolverExecutor
scriptExecutor ScriptExecutor
cacheExecutor CacheExecutor
scriptViewerExecutor ScriptViewerExecutor
checkerExecutor CheckerExecutor
installerExecutor InstallerExecutor
sourceExecutor SourceDownloaderExecutor
repos PackageFinder
// mgr manager.Manager
}
type BuildArgs struct {
Opts *types.BuildOpts
Info *distro.OSRelease
PkgFormat_ string
}
func (b *BuildArgs) BuildOpts() *types.BuildOpts {
return b.Opts
}
func (b *BuildArgs) OSRelease() *distro.OSRelease {
return b.Info
}
func (b *BuildArgs) PkgFormat() string {
return b.PkgFormat_
}
type BuildPackageFromDbArgs struct {
BuildArgs
Package *alrsh.Package
Packages []string
}
type BuildPackageFromScriptArgs struct {
BuildArgs
Script string
Packages []string
}
func (b *Builder) BuildPackageFromDb(
ctx context.Context,
args *BuildPackageFromDbArgs,
) ([]*BuiltDep, error) {
scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package)
return b.BuildPackage(ctx, &BuildInput{
script: scriptInfo.Script,
repository: scriptInfo.Repository,
packages: args.Packages,
pkgFormat: args.PkgFormat(),
opts: args.Opts,
info: args.Info,
})
}
func (b *Builder) BuildPackageFromScript(
ctx context.Context,
args *BuildPackageFromScriptArgs,
) ([]*BuiltDep, error) {
return b.BuildPackage(ctx, &BuildInput{
script: args.Script,
repository: "default",
packages: args.Packages,
pkgFormat: args.PkgFormat(),
opts: args.Opts,
info: args.Info,
})
}
func (b *Builder) BuildPackage(
ctx context.Context,
input *BuildInput,
) ([]*BuiltDep, error) {
scriptPath := input.script
slog.Debug("ReadScript")
sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath)
if err != nil {
return nil, fmt.Errorf("failed reading script: %w", err)
}
slog.Debug("ExecuteFirstPass")
basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf)
if err != nil {
return nil, fmt.Errorf("failed ExecuteFirstPass: %w", err)
}
var builtDeps []*BuiltDep
var remainingVars []*alrsh.Package
if !input.opts.Clean {
for _, vars := range varsOfPackages {
builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars)
if err != nil {
return nil, err
}
if ok {
builtDeps = append(builtDeps, &BuiltDep{
Path: builtPkgPath,
Name: vars.Name,
})
} else {
remainingVars = append(remainingVars, vars)
}
}
if len(remainingVars) == 0 {
slog.Info(gotext.Get("Using cached package"), "name", basePkg)
return builtDeps, nil
}
// Обновляем varsOfPackages только теми пакетами, которые нужно собрать
varsOfPackages = remainingVars
}
slog.Debug("ViewScript")
slog.Debug("", "varsOfPackages", varsOfPackages[0])
err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg)
if err != nil {
return nil, err
}
slog.Info(gotext.Get("Building package"), "name", basePkg)
for _, vars := range varsOfPackages {
cont, err := b.checkerExecutor.PerformChecks(ctx, input, vars)
if err != nil {
return nil, err
}
if !cont {
return nil, errors.New("exit...")
}
}
buildDepends := []string{}
optDepends := []string{}
depends := []string{}
sources := []string{}
checksums := []string{}
for _, vars := range varsOfPackages {
buildDepends = append(buildDepends, vars.BuildDepends.Resolved()...)
optDepends = append(optDepends, vars.OptDepends.Resolved()...)
depends = append(depends, vars.Depends.Resolved()...)
sources = append(sources, vars.Sources.Resolved()...)
checksums = append(checksums, vars.Checksums.Resolved()...)
}
buildDepends = removeDuplicates(buildDepends)
optDepends = removeDuplicates(optDepends)
depends = removeDuplicates(depends)
if len(sources) != len(checksums) {
slog.Error(gotext.Get("The checksums array must be the same length as sources"))
return nil, errors.New("exit...")
}
sources, checksums = removeDuplicatesSources(sources, checksums)
slog.Debug("installBuildDeps")
alrBuildDeps, installedBuildDeps, err := b.installBuildDeps(ctx, input, buildDepends)
if err != nil {
return nil, err
}
slog.Debug("installOptDeps")
_, err = b.installOptDeps(ctx, input, optDepends)
if err != nil {
return nil, err
}
depNames := make(map[string]struct{})
for _, dep := range alrBuildDeps {
depNames[dep.Name] = struct{}{}
}
// We filter so as not to re-build what has already been built at the `installBuildDeps` stage.
var filteredDepends []string
// Создаем набор подпакетов текущего мультипакета для исключения циклических зависимостей
currentPackageNames := make(map[string]struct{})
for _, pkg := range input.packages {
currentPackageNames[pkg] = struct{}{}
}
for _, d := range depends {
if _, found := depNames[d]; !found {
// Исключаем зависимости, которые являются подпакетами текущего мультипакета
if _, isCurrentPackage := currentPackageNames[d]; !isCurrentPackage {
filteredDepends = append(filteredDepends, d)
}
}
}
slog.Debug("BuildALRDeps")
newBuiltDeps, repoDeps, err := b.BuildALRDeps(ctx, input, filteredDepends)
if err != nil {
return nil, err
}
slog.Debug("PrepareDirs")
err = b.scriptExecutor.PrepareDirs(ctx, input, basePkg)
if err != nil {
return nil, err
}
slog.Info(gotext.Get("Downloading sources"))
slog.Debug("DownloadSources")
err = b.sourceExecutor.DownloadSources(
ctx,
input,
basePkg,
SourcesInput{
Sources: sources,
Checksums: checksums,
},
)
if err != nil {
return nil, err
}
builtDeps = removeDuplicates(append(builtDeps, newBuiltDeps...))
slog.Debug("ExecuteSecondPass")
res, err := b.scriptExecutor.ExecuteSecondPass(
ctx,
input,
sf,
varsOfPackages,
repoDeps,
builtDeps,
basePkg,
)
if err != nil {
return nil, err
}
builtDeps = removeDuplicates(append(builtDeps, res...))
err = b.removeBuildDeps(ctx, input, installedBuildDeps)
if err != nil {
return nil, err
}
return builtDeps, nil
}
func (b *Builder) removeBuildDeps(ctx context.Context, input interface {
BuildOptsProvider
}, deps []string,
) error {
if len(deps) > 0 {
remove, err := cliutils.YesNoPrompt(ctx, gotext.Get("Would you like to remove the build dependencies?"), input.BuildOpts().Interactive, false)
if err != nil {
return err
}
if remove {
err = b.installerExecutor.Remove(
ctx,
deps,
&manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
},
)
if err != nil {
return err
}
}
}
return nil
}
type InstallPkgsArgs struct {
BuildArgs
AlrPkgs []alrsh.Package
NativePkgs []string
}
func (b *Builder) InstallALRPackages(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
},
alrPkgs []alrsh.Package,
) error {
for _, pkg := range alrPkgs {
res, err := b.BuildPackageFromDb(
ctx,
&BuildPackageFromDbArgs{
Package: &pkg,
Packages: []string{},
BuildArgs: BuildArgs{
Opts: input.BuildOpts(),
Info: input.OSRelease(),
PkgFormat_: input.PkgFormat(),
},
},
)
if err != nil {
return err
}
err = b.installerExecutor.InstallLocal(
ctx,
GetBuiltPaths(res),
&manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
},
)
if err != nil {
return err
}
// Отслеживание установки ALR пакетов
for _, dep := range res {
if stats.ShouldTrackPackage(dep.Name) {
stats.TrackInstallation(ctx, dep.Name, "upgrade")
}
}
}
return nil
}
func (b *Builder) BuildALRDeps(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
},
depends []string,
) (buildDeps []*BuiltDep, repoDeps []string, err error) {
if len(depends) > 0 {
slog.Info(gotext.Get("Installing dependencies"))
found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей
if err != nil {
return nil, nil, fmt.Errorf("failed FindPkgs: %w", err)
}
repoDeps = notFound
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
// Для зависимостей указываем isDependency = true
pkgs := cliutils.FlattenPkgsWithContext(
ctx,
found,
"install",
input.BuildOpts().Interactive,
true,
)
type item struct {
pkg *alrsh.Package
packages []string
}
pkgsMap := make(map[string]*item)
for _, pkg := range pkgs {
name := pkg.BasePkgName
if name == "" {
name = pkg.Name
}
if pkgsMap[name] == nil {
pkgsMap[name] = &item{
pkg: &pkg,
}
}
pkgsMap[name].packages = append(
pkgsMap[name].packages,
pkg.Name,
)
}
for basePkgName := range pkgsMap {
pkg := pkgsMap[basePkgName].pkg
res, err := b.BuildPackageFromDb(
ctx,
&BuildPackageFromDbArgs{
Package: pkg,
Packages: pkgsMap[basePkgName].packages,
BuildArgs: BuildArgs{
Opts: input.BuildOpts(),
Info: input.OSRelease(),
PkgFormat_: input.PkgFormat(),
},
},
)
if err != nil {
return nil, nil, fmt.Errorf("failed build package from db: %w", err)
}
buildDeps = append(buildDeps, res...)
}
}
repoDeps = removeDuplicates(repoDeps)
buildDeps = removeDuplicates(buildDeps)
return buildDeps, repoDeps, nil
}
func (i *Builder) installBuildDeps(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
},
pkgs []string,
) ([]*BuiltDep, []string, error) {
var builtDeps []*BuiltDep
var deps []string
var err error
if len(pkgs) > 0 {
deps, err = i.installerExecutor.RemoveAlreadyInstalled(ctx, pkgs)
if err != nil {
return nil, nil, err
}
builtDeps, err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты
if err != nil {
return nil, nil, err
}
}
return builtDeps, deps, nil
}
func (i *Builder) installOptDeps(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
},
pkgs []string,
) ([]*BuiltDep, error) {
var builtDeps []*BuiltDep
optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(ctx, pkgs)
if err != nil {
return nil, err
}
if len(optDeps) > 0 {
optDeps, err := cliutils.ChooseOptDepends(
ctx,
optDeps,
"install",
input.BuildOpts().Interactive,
) // Пользователя просят выбрать опциональные зависимости
if err != nil {
return nil, err
}
if len(optDeps) == 0 {
return builtDeps, nil
}
builtDeps, err = i.InstallPkgs(ctx, input, optDeps) // Устанавливаем выбранные пакеты
if err != nil {
return nil, err
}
}
return builtDeps, nil
}
func (i *Builder) InstallPkgs(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
},
pkgs []string,
) ([]*BuiltDep, error) {
builtDeps, repoDeps, err := i.BuildALRDeps(ctx, input, pkgs)
if err != nil {
return nil, err
}
if len(builtDeps) > 0 {
err = i.installerExecutor.InstallLocal(ctx, GetBuiltPaths(builtDeps), &manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
})
if err != nil {
return nil, err
}
// Отслеживание установки локальных пакетов
for _, dep := range builtDeps {
if stats.ShouldTrackPackage(dep.Name) {
stats.TrackInstallation(ctx, dep.Name, "install")
}
}
}
if len(repoDeps) > 0 {
err = i.installerExecutor.Install(ctx, repoDeps, &manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
})
if err != nil {
return nil, err
}
// Отслеживание установки пакетов из репозитория
for _, pkg := range repoDeps {
if stats.ShouldTrackPackage(pkg) {
stats.TrackInstallation(ctx, pkg, "install")
}
}
}
return builtDeps, nil
}

View File

@@ -1,286 +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 build
import (
"context"
"fmt"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
type TestPackageFinder struct {
FindPkgsFunc func(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error)
}
func (pf *TestPackageFinder) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
if pf.FindPkgsFunc != nil {
return pf.FindPkgsFunc(ctx, pkgs)
}
return map[string][]db.Package{}, []string{}, nil
}
type TestManager struct {
NameFunc func() string
FormatFunc func() string
ExistsFunc func() bool
SetRootCmdFunc func(cmd string)
SyncFunc func(opts *manager.Opts) error
InstallFunc func(opts *manager.Opts, pkgs ...string) error
RemoveFunc func(opts *manager.Opts, pkgs ...string) error
UpgradeFunc func(opts *manager.Opts, pkgs ...string) error
InstallLocalFunc func(opts *manager.Opts, files ...string) error
UpgradeAllFunc func(opts *manager.Opts) error
ListInstalledFunc func(opts *manager.Opts) (map[string]string, error)
IsInstalledFunc func(pkg string) (bool, error)
}
func (m *TestManager) Name() string {
if m.NameFunc != nil {
return m.NameFunc()
}
return "TestManager"
}
func (m *TestManager) Format() string {
if m.FormatFunc != nil {
return m.FormatFunc()
}
return "testpkg"
}
func (m *TestManager) Exists() bool {
if m.ExistsFunc != nil {
return m.ExistsFunc()
}
return true
}
func (m *TestManager) SetRootCmd(cmd string) {
if m.SetRootCmdFunc != nil {
m.SetRootCmdFunc(cmd)
}
}
func (m *TestManager) Sync(opts *manager.Opts) error {
if m.SyncFunc != nil {
return m.SyncFunc(opts)
}
return nil
}
func (m *TestManager) Install(opts *manager.Opts, pkgs ...string) error {
if m.InstallFunc != nil {
return m.InstallFunc(opts, pkgs...)
}
return nil
}
func (m *TestManager) Remove(opts *manager.Opts, pkgs ...string) error {
if m.RemoveFunc != nil {
return m.RemoveFunc(opts, pkgs...)
}
return nil
}
func (m *TestManager) Upgrade(opts *manager.Opts, pkgs ...string) error {
if m.UpgradeFunc != nil {
return m.UpgradeFunc(opts, pkgs...)
}
return nil
}
func (m *TestManager) InstallLocal(opts *manager.Opts, files ...string) error {
if m.InstallLocalFunc != nil {
return m.InstallLocalFunc(opts, files...)
}
return nil
}
func (m *TestManager) UpgradeAll(opts *manager.Opts) error {
if m.UpgradeAllFunc != nil {
return m.UpgradeAllFunc(opts)
}
return nil
}
func (m *TestManager) ListInstalled(opts *manager.Opts) (map[string]string, error) {
if m.ListInstalledFunc != nil {
return m.ListInstalledFunc(opts)
}
return map[string]string{}, nil
}
func (m *TestManager) IsInstalled(pkg string) (bool, error) {
if m.IsInstalledFunc != nil {
return m.IsInstalledFunc(pkg)
}
return true, nil
}
type TestConfig struct{}
func (c *TestConfig) PagerStyle() string {
return "native"
}
func (c *TestConfig) GetPaths() *config.Paths {
return &config.Paths{
CacheDir: "/tmp",
}
}
func TestExecuteFirstPassIsSecure(t *testing.T) {
cfg := &TestConfig{}
pf := &TestPackageFinder{}
info := &distro.OSRelease{}
m := &TestManager{}
opts := types.BuildOpts{
Manager: m,
Interactive: false,
}
ctx := context.Background()
b := NewBuilder(
ctx,
opts,
pf,
info,
cfg,
)
tmpFile, err := os.CreateTemp("", "testfile-")
assert.NoError(t, err)
tmpFilePath := tmpFile.Name()
defer os.Remove(tmpFilePath)
_, err = os.Stat(tmpFilePath)
assert.NoError(t, err)
testScript := fmt.Sprintf(`name='test'
version=1.0.0
release=1
rm -f %s`, tmpFilePath)
fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "alr.sh")
assert.NoError(t, err)
_, _, err = b.executeFirstPass(fl)
assert.NoError(t, err)
_, err = os.Stat(tmpFilePath)
assert.NoError(t, err)
}
func TestExecuteFirstPassIsCorrect(t *testing.T) {
type testCase struct {
Name string
Script string
Opts types.BuildOpts
Expected func(t *testing.T, vars []*types.BuildVars)
}
for _, tc := range []testCase{{
Name: "single package",
Script: `name='test'
version='1.0.0'
release=1
epoch=2
desc="Test package"
homepage='https://example.com'
maintainer='Ivan Ivanov'
`,
Opts: types.BuildOpts{
Manager: &TestManager{},
Interactive: false,
},
Expected: func(t *testing.T, vars []*types.BuildVars) {
assert.Equal(t, 1, len(vars))
assert.Equal(t, vars[0].Name, "test")
assert.Equal(t, vars[0].Version, "1.0.0")
assert.Equal(t, vars[0].Release, int(1))
assert.Equal(t, vars[0].Epoch, uint(2))
assert.Equal(t, vars[0].Description, "Test package")
},
}, {
Name: "multiple packages",
Script: `name=(
foo
bar
)
version='0.0.1'
release=1
epoch=2
desc="Test package"
meta_foo() {
desc="Foo package"
}
meta_bar() {
}
`,
Opts: types.BuildOpts{
Packages: []string{"foo"},
Manager: &TestManager{},
Interactive: false,
},
Expected: func(t *testing.T, vars []*types.BuildVars) {
assert.Equal(t, 1, len(vars))
assert.Equal(t, vars[0].Name, "foo")
assert.Equal(t, vars[0].Description, "Foo package")
},
}} {
t.Run(tc.Name, func(t *testing.T) {
cfg := &TestConfig{}
pf := &TestPackageFinder{}
info := &distro.OSRelease{}
ctx := context.Background()
b := NewBuilder(
ctx,
tc.Opts,
pf,
info,
cfg,
)
fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh")
assert.NoError(t, err)
_, allVars, err := b.scriptExecutor.ExecuteSecondPass(fl)
assert.NoError(t, err)
tc.Expected(t, allVars)
})
}
}

View File

@@ -1,69 +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 build
import (
"context"
"os"
"path/filepath"
"github.com/goreleaser/nfpm/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type Cache struct {
cfg Config
}
func (c *Cache) CheckForBuiltPackage(
ctx context.Context,
input *BuildInput,
vars *alrsh.Package,
) (string, bool, error) {
filename, err := pkgFileName(input, vars)
if err != nil {
return "", false, err
}
pkgPath := filepath.Join(getBaseDir(c.cfg, vars.Name), filename)
_, err = os.Stat(pkgPath)
if err != nil {
return "", false, nil
}
return pkgPath, true, nil
}
func pkgFileName(
input interface {
OsInfoProvider
PkgFormatProvider
RepositoryProvider
},
vars *alrsh.Package,
) (string, error) {
pkgInfo := getBasePkgInfo(vars, input)
packager, err := nfpm.Get(input.PkgFormat())
if err != nil {
return "", err
}
return packager.ConventionalFileName(pkgInfo), nil
}

View File

@@ -1,74 +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 build
import (
"context"
"log/slog"
"github.com/leonelquinteros/gotext"
"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/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type Checker struct {
mgr manager.Manager
}
func (c *Checker) PerformChecks(
ctx context.Context,
input *BuildInput,
vars *alrsh.Package,
) (bool, error) {
if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры
cont, err := cliutils.YesNoPrompt(
ctx,
gotext.Get("Your system's CPU architecture doesn't match this package. Do you want to build anyway?"),
input.opts.Interactive,
true,
)
if err != nil {
return false, err
}
if !cont {
return false, nil
}
}
installed, err := c.mgr.ListInstalled(nil)
if err != nil {
return false, err
}
filename, err := pkgFileName(input, vars)
if err != nil {
return false, err
}
if instVer, ok := installed[filename]; ok { // Если пакет уже установлен, выводим предупреждение
slog.Warn(gotext.Get("This package is already installed"),
"name", vars.Name,
"version", instVer,
)
}
return true, nil
}

View File

@@ -1,71 +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 build
import (
"path/filepath"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type BaseDirProvider interface {
BaseDir() string
}
type SrcDirProvider interface {
SrcDir() string
}
type PkgDirProvider interface {
PkgDir() string
}
type ScriptDirProvider interface {
ScriptDir() string
}
func getDirs(
cfg Config,
scriptPath string,
basePkg string,
) (types.Directories, error) {
pkgsDir := cfg.GetPaths().PkgsDir
scriptPath, err := filepath.Abs(scriptPath)
if err != nil {
return types.Directories{}, err
}
baseDir := filepath.Join(pkgsDir, basePkg)
return types.Directories{
BaseDir: getBaseDir(cfg, basePkg),
SrcDir: getSrcDir(cfg, basePkg),
PkgDir: filepath.Join(baseDir, "pkg"),
ScriptDir: getScriptDir(scriptPath),
}, nil
}
func getBaseDir(cfg Config, basePkg string) string {
return filepath.Join(cfg.GetPaths().PkgsDir, basePkg)
}
func getSrcDir(cfg Config, basePkg string) string {
return filepath.Join(getBaseDir(cfg, basePkg), "src")
}
func getScriptDir(scriptPath string) string {
return filepath.Dir(scriptPath)
}

View File

@@ -1,96 +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 finddeps
import (
"bytes"
"context"
"log/slog"
"os/exec"
"path"
"strings"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"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 {
if _, err := exec.LookPath(command); err != nil {
slog.Info(gotext.Get("Command not found on the system"), "command", command)
return nil
}
var paths []string
for _, content := range pkgInfo.Contents {
if content.Type != "dir" {
paths = append(paths,
path.Join(dirs.PkgDir, content.Destination),
)
}
}
if len(paths) == 0 {
return nil
}
cmd := exec.CommandContext(ctx, command)
cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n") + "\n")
cmd.Env = append(cmd.Env,
"RPM_BUILD_ROOT="+dirs.PkgDir,
"RPM_FINDPROV_METHOD=",
"RPM_FINDREQ_METHOD=",
"RPM_DATADIR=",
"RPM_SUBPACKAGE_NAME=",
)
cmd.Env = append(cmd.Env, envs...)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
slog.Error(stderr.String())
return err
}
slog.Debug(stderr.String())
dependencies := strings.Split(strings.TrimSpace(out.String()), "\n")
for _, dep := range dependencies {
if dep != "" {
updateFunc(dep)
}
}
return nil
}
type ALTLinuxFindProvReq struct{}
func (o *ALTLinuxFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependenciesALTLinux(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-provides", []string{"RPM_FINDPROV_SKIPLIST=" + strings.Join(skiplist, "\n")}, func(dep string) {
slog.Info(gotext.Get("Provided dependency found"), "dep", dep)
pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep)
})
}
func (o *ALTLinuxFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependenciesALTLinux(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", []string{"RPM_FINDREQ_SKIPLIST=" + strings.Join(skiplist, "\n")}, func(dep string) {
slog.Info(gotext.Get("Required dependency found"), "dep", dep)
pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep)
})
}

View File

@@ -1,39 +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 finddeps
import (
"context"
"log/slog"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type EmptyFindProvReq struct{}
func (o *EmptyFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
slog.Info(gotext.Get("AutoProv is not implemented for this package format, so it's skipped"))
return nil
}
func (o *EmptyFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
slog.Info(gotext.Get("AutoReq is not implemented for this package format, so it's skipped"))
return nil
}

View File

@@ -1,118 +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 finddeps
import (
"bytes"
"context"
"fmt"
"log/slog"
"os/exec"
"path"
"strings"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type FedoraFindProvReq struct{}
func rpmFindDependenciesFedora(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, args []string, updateFunc func(string)) error {
if _, err := exec.LookPath(command); err != nil {
slog.Info(gotext.Get("Command not found on the system"), "command", command)
return nil
}
var paths []string
for _, content := range pkgInfo.Contents {
if content.Type != "dir" {
paths = append(paths,
path.Join(dirs.PkgDir, content.Destination),
)
}
}
if len(paths) == 0 {
return nil
}
cmd := exec.CommandContext(ctx, command, args...)
cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n") + "\n")
cmd.Env = append(cmd.Env,
"RPM_BUILD_ROOT="+dirs.PkgDir,
)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
slog.Error(stderr.String())
return err
}
slog.Debug(stderr.String())
dependencies := strings.Split(strings.TrimSpace(out.String()), "\n")
for _, dep := range dependencies {
if dep != "" {
updateFunc(dep)
}
}
return nil
}
func (o *FedoraFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependenciesFedora(
ctx,
pkgInfo,
dirs,
"/usr/lib/rpm/rpmdeps",
[]string{
"--define=_use_internal_dependency_generator 1",
"--provides",
fmt.Sprintf(
"--define=__provides_exclude_from %s\"",
strings.Join(skiplist, "|"),
),
},
func(dep string) {
slog.Info(gotext.Get("Provided dependency found"), "dep", dep)
pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep)
})
}
func (o *FedoraFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependenciesFedora(
ctx,
pkgInfo,
dirs,
"/usr/lib/rpm/rpmdeps",
[]string{
"--define=_use_internal_dependency_generator 1",
"--requires",
fmt.Sprintf(
"--define=__requires_exclude_from %s",
strings.Join(skiplist, "|"),
),
},
func(dep string) {
slog.Info(gotext.Get("Required dependency found"), "dep", dep)
pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep)
})
}

View File

@@ -1,58 +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 finddeps
import (
"context"
"github.com/goreleaser/nfpm/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type ProvReqFinder interface {
FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error
FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error
}
type ProvReqService struct {
finder ProvReqFinder
}
func New(info *distro.OSRelease, pkgFormat string) *ProvReqService {
s := &ProvReqService{
finder: &EmptyFindProvReq{},
}
if pkgFormat == "rpm" {
switch info.ID {
case "altlinux":
s.finder = &ALTLinuxFindProvReq{}
case "fedora":
s.finder = &FedoraFindProvReq{}
}
}
return s
}
func (s *ProvReqService) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return s.finder.FindProvides(ctx, pkgInfo, dirs, skiplist)
}
func (s *ProvReqService) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return s.finder.FindRequires(ctx, pkgInfo, dirs, skiplist)
}

View File

@@ -1,296 +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 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/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 moveWithSymlinkHandling(src, dst string) error {
srcInfo, err := os.Lstat(src)
if err != nil {
return fmt.Errorf("failed to get source info: %w", err)
}
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
return fmt.Errorf("failed to create destination directory: %w", err)
}
if srcInfo.Mode()&os.ModeSymlink != 0 {
return moveSymlink(src, dst)
}
if err := os.Rename(src, dst); err != nil {
return copyAndRemove(src, dst)
}
return nil
}
func moveSymlink(src, dst string) error {
target, err := os.Readlink(src)
if err != nil {
return fmt.Errorf("failed to read symlink: %w", err)
}
if err := os.Symlink(target, dst); err != nil {
return fmt.Errorf("failed to create symlink: %w", err)
}
if err := os.Remove(src); err != nil {
os.Remove(dst)
return fmt.Errorf("failed to remove original symlink: %w", err)
}
return nil
}
func copyAndRemove(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("failed to open source: %w", err)
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return fmt.Errorf("failed to create destination: %w", err)
}
defer dstFile.Close()
if _, err := io.Copy(dstFile, srcFile); err != nil {
return fmt.Errorf("failed to copy content: %w", err)
}
srcInfo, err := srcFile.Stat()
if err != nil {
return fmt.Errorf("failed to get source stats: %w", err)
}
if err := dstFile.Chmod(srcInfo.Mode()); err != nil {
return fmt.Errorf("failed to set permissions: %w", err)
}
if err := os.Remove(src); err != nil {
return fmt.Errorf("failed to remove source: %w", err)
}
return nil
}
func moveFileWithErrorHandling(src, dst string) error {
err := moveWithSymlinkHandling(src, dst)
if err != nil {
if os.IsPermission(err) {
return fmt.Errorf("permission denied: %w", err)
}
if os.IsNotExist(err) {
return fmt.Errorf("source file does not exist: %w", err)
}
return fmt.Errorf("failed to move file: %w", err)
}
return nil
}
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 := moveFileWithErrorHandling(filepath.Join(dirs.PkgDir, content.Destination), filepath.Join(dirs.PkgDir, origFilePath)); err != nil {
return nil, fmt.Errorf("failed to move original binary: %w", err)
}
content.Type = "file"
content.Source = filepath.Join(dirs.PkgDir, content.Destination)
// Create wrapper script
if err := createWrapperScript(filepath.Join(dirs.PkgDir, content.Destination), origFilePath, dest); err != nil {
return nil, fmt.Errorf("failed to create wrapper script: %w", err)
}
return buildContents(pkg, dirs, &[]string{
origFilePath,
dest,
})
}
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

@@ -1,322 +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 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)
binDir := filepath.Join(pkgDir, "usr", "bin")
os.MkdirAll(binDir, 0o755)
srcBinary := filepath.Join(binDir, "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)
binDir := filepath.Join(pkgDir, "usr", "bin")
os.MkdirAll(binDir, 0o755)
srcBinary := filepath.Join(binDir, "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

@@ -1,60 +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 build
import (
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
func NewInstaller(mgr manager.Manager) *Installer {
return &Installer{
mgr: mgr,
}
}
type Installer struct{ mgr manager.Manager }
func (i *Installer) InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error {
return i.mgr.InstallLocal(opts, paths...)
}
func (i *Installer) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error {
return i.mgr.Install(opts, pkgs...)
}
func (i *Installer) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error {
return i.mgr.Remove(opts, pkgs...)
}
func (i *Installer) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error) {
filteredPackages := []string{}
for _, dep := range pkgs {
installed, err := i.mgr.IsInstalled(dep)
if err != nil {
return nil, err
}
if installed {
continue
}
filteredPackages = append(filteredPackages, dep)
}
return filteredPackages, nil
}

View File

@@ -1,52 +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 build
import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
func NewMainBuilder(
cfg Config,
mgr manager.Manager,
repos PackageFinder,
scriptExecutor ScriptExecutor,
installerExecutor InstallerExecutor,
) (*Builder, error) {
builder := &Builder{
scriptExecutor: scriptExecutor,
cacheExecutor: &Cache{
cfg,
},
scriptResolver: &ScriptResolver{
cfg,
},
scriptViewerExecutor: &ScriptViewer{
config: cfg,
},
checkerExecutor: &Checker{
mgr,
},
installerExecutor: installerExecutor,
sourceExecutor: &SourceDownloader{
cfg,
},
repos: repos,
}
return builder, nil
}

View File

@@ -1,142 +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 build
import (
"fmt"
"log/slog"
"os"
"os/exec"
"strings"
"sync"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
)
var pluginMap = map[string]plugin.Plugin{
"script-executor": &ScriptExecutorPlugin{},
"installer": &InstallerExecutorPlugin{},
"repos": &ReposExecutorPlugin{},
}
var HandshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "ALR_PLUGIN",
MagicCookieValue: "-",
}
func setCommonCmdEnv(cmd *exec.Cmd) {
cmd.Env = []string{
"HOME=" + os.Getenv("HOME"),
"LOGNAME=" + os.Getenv("USER"),
"USER=" + os.Getenv("USER"),
"PATH=/usr/bin:/bin:/usr/local/bin",
}
for _, env := range os.Environ() {
if strings.HasPrefix(env, "LANG=") ||
strings.HasPrefix(env, "LANGUAGE=") ||
strings.HasPrefix(env, "LC_") ||
strings.HasPrefix(env, "ALR_LOG_LEVEL=") {
cmd.Env = append(cmd.Env, env)
}
}
}
func GetPluginServeCommonConfig() *plugin.ServeConfig {
return &plugin.ServeConfig{
HandshakeConfig: HandshakeConfig,
Logger: hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Output: os.Stderr,
Level: hclog.Trace,
JSONFormat: true,
DisableTime: true,
}),
}
}
func GetSafeInstaller() (InstallerExecutor, func(), error) {
return getSafeExecutor[InstallerExecutor]("_internal-installer", "installer")
}
func GetSafeScriptExecutor() (ScriptExecutor, func(), error) {
return getSafeExecutor[ScriptExecutor]("_internal-safe-script-executor", "script-executor")
}
func GetSafeReposExecutor() (ReposExecutor, func(), error) {
return getSafeExecutor[ReposExecutor]("_internal-repos", "repos")
}
func getSafeExecutor[T any](subCommand, pluginName string) (T, func(), error) {
var err error
executable, err := os.Executable()
if err != nil {
var zero T
return zero, nil, err
}
cmd := exec.Command(executable, subCommand)
setCommonCmdEnv(cmd)
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig,
Plugins: pluginMap,
Cmd: cmd,
Logger: logger.GetHCLoggerAdapter(),
SkipHostEnv: true,
UnixSocketConfig: &plugin.UnixSocketConfig{},
SyncStderr: os.Stderr,
})
rpcClient, err := client.Client()
if err != nil {
var zero T
return zero, nil, err
}
var cleanupOnce sync.Once
cleanup := func() {
cleanupOnce.Do(func() {
client.Kill()
})
}
defer func() {
if err != nil {
slog.Debug("close executor")
cleanup()
}
}()
raw, err := rpcClient.Dispense(pluginName)
if err != nil {
var zero T
return zero, nil, err
}
executor, ok := raw.(T)
if !ok {
var zero T
err = fmt.Errorf("dispensed object is not a %T (got %T)", zero, raw)
return zero, nil, err
}
return executor, cleanup, nil
}

View File

@@ -1,60 +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 build
import (
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
//go:generate go run ../../generators/plugin-generator InstallerExecutor ScriptExecutor ReposExecutor
// The Executors interfaces must use context.Context as the first parameter,
// because the plugin-generator cannot generate code without it.
type InstallerExecutor interface {
InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error
Install(ctx context.Context, pkgs []string, opts *manager.Opts) error
Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error
RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error)
}
type ScriptExecutor interface {
ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error)
ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error)
PrepareDirs(
ctx context.Context,
input *BuildInput,
basePkg string,
) error
ExecuteSecondPass(
ctx context.Context,
input *BuildInput,
sf *alrsh.ScriptFile,
varsOfPackages []*alrsh.Package,
repoDeps []string,
builtDeps []*BuiltDep,
basePkg string,
) ([]*BuiltDep, error)
}
type ReposExecutor interface {
PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error)
}

View File

@@ -1,369 +0,0 @@
// DO NOT EDIT MANUALLY. This file is generated.
// 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 (
"net/rpc"
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
"github.com/hashicorp/go-plugin"
)
type InstallerExecutorPlugin struct {
Impl InstallerExecutor
}
type InstallerExecutorRPCServer struct {
Impl InstallerExecutor
}
type InstallerExecutorRPC struct {
client *rpc.Client
}
func (p *InstallerExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &InstallerExecutorRPC{client: c}, nil
}
func (p *InstallerExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &InstallerExecutorRPCServer{Impl: p.Impl}, nil
}
type ScriptExecutorPlugin struct {
Impl ScriptExecutor
}
type ScriptExecutorRPCServer struct {
Impl ScriptExecutor
}
type ScriptExecutorRPC struct {
client *rpc.Client
}
func (p *ScriptExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &ScriptExecutorRPC{client: c}, nil
}
func (p *ScriptExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &ScriptExecutorRPCServer{Impl: p.Impl}, nil
}
type ReposExecutorPlugin struct {
Impl ReposExecutor
}
type ReposExecutorRPCServer struct {
Impl ReposExecutor
}
type ReposExecutorRPC struct {
client *rpc.Client
}
func (p *ReposExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &ReposExecutorRPC{client: c}, nil
}
func (p *ReposExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &ReposExecutorRPCServer{Impl: p.Impl}, nil
}
type InstallerExecutorInstallLocalArgs struct {
Paths []string
Opts *manager.Opts
}
type InstallerExecutorInstallLocalResp struct {
}
func (s *InstallerExecutorRPC) InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error {
var resp *InstallerExecutorInstallLocalResp
err := s.client.Call("Plugin.InstallLocal", &InstallerExecutorInstallLocalArgs{
Paths: paths,
Opts: opts,
}, &resp)
if err != nil {
return err
}
return nil
}
func (s *InstallerExecutorRPCServer) InstallLocal(args *InstallerExecutorInstallLocalArgs, resp *InstallerExecutorInstallLocalResp) error {
err := s.Impl.InstallLocal(context.Background(), args.Paths, args.Opts)
if err != nil {
return err
}
*resp = InstallerExecutorInstallLocalResp{}
return nil
}
type InstallerExecutorInstallArgs struct {
Pkgs []string
Opts *manager.Opts
}
type InstallerExecutorInstallResp struct {
}
func (s *InstallerExecutorRPC) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error {
var resp *InstallerExecutorInstallResp
err := s.client.Call("Plugin.Install", &InstallerExecutorInstallArgs{
Pkgs: pkgs,
Opts: opts,
}, &resp)
if err != nil {
return err
}
return nil
}
func (s *InstallerExecutorRPCServer) Install(args *InstallerExecutorInstallArgs, resp *InstallerExecutorInstallResp) error {
err := s.Impl.Install(context.Background(), args.Pkgs, args.Opts)
if err != nil {
return err
}
*resp = InstallerExecutorInstallResp{}
return nil
}
type InstallerExecutorRemoveArgs struct {
Pkgs []string
Opts *manager.Opts
}
type InstallerExecutorRemoveResp struct {
}
func (s *InstallerExecutorRPC) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error {
var resp *InstallerExecutorRemoveResp
err := s.client.Call("Plugin.Remove", &InstallerExecutorRemoveArgs{
Pkgs: pkgs,
Opts: opts,
}, &resp)
if err != nil {
return err
}
return nil
}
func (s *InstallerExecutorRPCServer) Remove(args *InstallerExecutorRemoveArgs, resp *InstallerExecutorRemoveResp) error {
err := s.Impl.Remove(context.Background(), args.Pkgs, args.Opts)
if err != nil {
return err
}
*resp = InstallerExecutorRemoveResp{}
return nil
}
type InstallerExecutorRemoveAlreadyInstalledArgs struct {
Pkgs []string
}
type InstallerExecutorRemoveAlreadyInstalledResp struct {
Result0 []string
}
func (s *InstallerExecutorRPC) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error) {
var resp *InstallerExecutorRemoveAlreadyInstalledResp
err := s.client.Call("Plugin.RemoveAlreadyInstalled", &InstallerExecutorRemoveAlreadyInstalledArgs{
Pkgs: pkgs,
}, &resp)
if err != nil {
return nil, err
}
return resp.Result0, nil
}
func (s *InstallerExecutorRPCServer) RemoveAlreadyInstalled(args *InstallerExecutorRemoveAlreadyInstalledArgs, resp *InstallerExecutorRemoveAlreadyInstalledResp) error {
result0, err := s.Impl.RemoveAlreadyInstalled(context.Background(), args.Pkgs)
if err != nil {
return err
}
*resp = InstallerExecutorRemoveAlreadyInstalledResp{
Result0: result0,
}
return nil
}
type ScriptExecutorReadScriptArgs struct {
ScriptPath string
}
type ScriptExecutorReadScriptResp struct {
Result0 *alrsh.ScriptFile
}
func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
var resp *ScriptExecutorReadScriptResp
err := s.client.Call("Plugin.ReadScript", &ScriptExecutorReadScriptArgs{
ScriptPath: scriptPath,
}, &resp)
if err != nil {
return nil, err
}
return resp.Result0, nil
}
func (s *ScriptExecutorRPCServer) ReadScript(args *ScriptExecutorReadScriptArgs, resp *ScriptExecutorReadScriptResp) error {
result0, err := s.Impl.ReadScript(context.Background(), args.ScriptPath)
if err != nil {
return err
}
*resp = ScriptExecutorReadScriptResp{
Result0: result0,
}
return nil
}
type ScriptExecutorExecuteFirstPassArgs struct {
Input *BuildInput
Sf *alrsh.ScriptFile
}
type ScriptExecutorExecuteFirstPassResp struct {
Result0 string
Result1 []*alrsh.Package
}
func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
var resp *ScriptExecutorExecuteFirstPassResp
err := s.client.Call("Plugin.ExecuteFirstPass", &ScriptExecutorExecuteFirstPassArgs{
Input: input,
Sf: sf,
}, &resp)
if err != nil {
return "", nil, err
}
return resp.Result0, resp.Result1, nil
}
func (s *ScriptExecutorRPCServer) ExecuteFirstPass(args *ScriptExecutorExecuteFirstPassArgs, resp *ScriptExecutorExecuteFirstPassResp) error {
result0, result1, err := s.Impl.ExecuteFirstPass(context.Background(), args.Input, args.Sf)
if err != nil {
return err
}
*resp = ScriptExecutorExecuteFirstPassResp{
Result0: result0,
Result1: result1,
}
return nil
}
type ScriptExecutorPrepareDirsArgs struct {
Input *BuildInput
BasePkg string
}
type ScriptExecutorPrepareDirsResp struct {
}
func (s *ScriptExecutorRPC) PrepareDirs(ctx context.Context, input *BuildInput, basePkg string) error {
var resp *ScriptExecutorPrepareDirsResp
err := s.client.Call("Plugin.PrepareDirs", &ScriptExecutorPrepareDirsArgs{
Input: input,
BasePkg: basePkg,
}, &resp)
if err != nil {
return err
}
return nil
}
func (s *ScriptExecutorRPCServer) PrepareDirs(args *ScriptExecutorPrepareDirsArgs, resp *ScriptExecutorPrepareDirsResp) error {
err := s.Impl.PrepareDirs(context.Background(), args.Input, args.BasePkg)
if err != nil {
return err
}
*resp = ScriptExecutorPrepareDirsResp{}
return nil
}
type ScriptExecutorExecuteSecondPassArgs struct {
Input *BuildInput
Sf *alrsh.ScriptFile
VarsOfPackages []*alrsh.Package
RepoDeps []string
BuiltDeps []*BuiltDep
BasePkg string
}
type ScriptExecutorExecuteSecondPassResp struct {
Result0 []*BuiltDep
}
func (s *ScriptExecutorRPC) ExecuteSecondPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile, varsOfPackages []*alrsh.Package, repoDeps []string, builtDeps []*BuiltDep, basePkg string) ([]*BuiltDep, error) {
var resp *ScriptExecutorExecuteSecondPassResp
err := s.client.Call("Plugin.ExecuteSecondPass", &ScriptExecutorExecuteSecondPassArgs{
Input: input,
Sf: sf,
VarsOfPackages: varsOfPackages,
RepoDeps: repoDeps,
BuiltDeps: builtDeps,
BasePkg: basePkg,
}, &resp)
if err != nil {
return nil, err
}
return resp.Result0, nil
}
func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ScriptExecutorExecuteSecondPassArgs, resp *ScriptExecutorExecuteSecondPassResp) error {
result0, err := s.Impl.ExecuteSecondPass(context.Background(), args.Input, args.Sf, args.VarsOfPackages, args.RepoDeps, args.BuiltDeps, args.BasePkg)
if err != nil {
return err
}
*resp = ScriptExecutorExecuteSecondPassResp{
Result0: result0,
}
return nil
}
type ReposExecutorPullOneAndUpdateFromConfigArgs struct {
Repo *types.Repo
}
type ReposExecutorPullOneAndUpdateFromConfigResp struct {
Result0 types.Repo
}
func (s *ReposExecutorRPC) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error) {
var resp *ReposExecutorPullOneAndUpdateFromConfigResp
err := s.client.Call("Plugin.PullOneAndUpdateFromConfig", &ReposExecutorPullOneAndUpdateFromConfigArgs{
Repo: repo,
}, &resp)
if err != nil {
return types.Repo{}, err
}
return resp.Result0, nil
}
func (s *ReposExecutorRPCServer) PullOneAndUpdateFromConfig(args *ReposExecutorPullOneAndUpdateFromConfigArgs, resp *ReposExecutorPullOneAndUpdateFromConfigResp) error {
result0, err := s.Impl.PullOneAndUpdateFromConfig(context.Background(), args.Repo)
if err != nil {
return err
}
*resp = ReposExecutorPullOneAndUpdateFromConfigResp{
Result0: result0,
}
return nil
}

View File

@@ -1,37 +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 build
import (
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type reposExecutor struct{ r *repos.Repos }
func NewRepos(r *repos.Repos) ReposExecutor {
return &reposExecutor{r}
}
func (r *reposExecutor) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error) {
if err := r.r.PullOneAndUpdateFromConfig(ctx, repo); err != nil {
return *repo, err
}
return *repo, nil
}

View File

@@ -1,379 +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 build
import (
"bytes"
"context"
"fmt"
"log/slog"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"time"
"github.com/google/shlex"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
finddeps "gitea.plemya-x.ru/Plemya-x/ALR/internal/build/find_deps"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type LocalScriptExecutor struct {
cfg Config
}
func NewLocalScriptExecutor(cfg Config) *LocalScriptExecutor {
return &LocalScriptExecutor{
cfg,
}
}
func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
return alrsh.ReadFromLocal(scriptPath)
}
func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
return sf.ParseBuildVars(ctx, input.info, input.packages)
}
func (e *LocalScriptExecutor) PrepareDirs(
ctx context.Context,
input *BuildInput,
basePkg string,
) error {
dirs, err := getDirs(
e.cfg,
input.script,
basePkg,
)
if err != nil {
return err
}
err = prepareDirs(dirs)
if err != nil {
return err
}
return nil
}
func (e *LocalScriptExecutor) ExecuteSecondPass(
ctx context.Context,
input *BuildInput,
sf *alrsh.ScriptFile,
varsOfPackages []*alrsh.Package,
repoDeps []string,
builtDeps []*BuiltDep,
basePkg string,
) ([]*BuiltDep, error) {
dirs, err := getDirs(e.cfg, sf.Path(), basePkg)
if err != nil {
return nil, err
}
env := createBuildEnvVars(input.info, dirs)
fakeroot := handlers.FakerootExecHandler(2 * time.Second)
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
interp.StdIO(os.Stdin, os.Stderr, os.Stderr), // Устанавливаем стандартный ввод-вывод
interp.ExecHandlers(func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
return helpers.Helpers.ExecHandler(fakeroot)
}), // Обрабатываем выполнение через fakeroot
)
if err != nil {
return nil, err
}
err = runner.Run(ctx, sf.File())
if err != nil {
return nil, err
}
dec := decoder.New(input.info, runner)
// var builtPaths []string
err = e.ExecuteFunctions(ctx, dirs, dec)
if err != nil {
return nil, err
}
for _, vars := range varsOfPackages {
packageName := ""
if vars.BasePkgName != "" {
packageName = vars.Name
}
pkgFormat := input.pkgFormat
funcOut, err := e.ExecutePackageFunctions(
ctx,
dec,
dirs,
packageName,
)
if err != nil {
return nil, err
}
slog.Info(gotext.Get("Building package metadata"), "name", basePkg)
pkgInfo, err := buildPkgMetadata(
ctx,
input,
vars,
dirs,
append(
repoDeps,
GetBuiltName(builtDeps)...,
),
funcOut.Contents,
)
if err != nil {
return nil, err
}
packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета
if err != nil {
return nil, err
}
pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета
pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету
slog.Info("Creating package file", "path", pkgPath, "name", pkgName)
pkgFile, err := os.Create(pkgPath)
if err != nil {
slog.Error("Failed to create package file", "path", pkgPath, "error", err)
return nil, err
}
defer pkgFile.Close()
slog.Info("Packaging with nfpm", "format", pkgFormat)
err = packager.Package(pkgInfo, pkgFile)
if err != nil {
slog.Error("Failed to create package", "path", pkgPath, "error", err)
return nil, err
}
slog.Info("Package created successfully", "path", pkgPath)
// Проверяем, что файл действительно существует
if _, err := os.Stat(pkgPath); err != nil {
slog.Error("Package file not found after creation", "path", pkgPath, "error", err)
return nil, err
}
slog.Info("Package file verified to exist", "path", pkgPath)
builtDeps = append(builtDeps, &BuiltDep{
Name: vars.Name,
Path: pkgPath,
})
}
return builtDeps, nil
}
func buildPkgMetadata(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
RepositoryProvider
},
vars *alrsh.Package,
dirs types.Directories,
deps []string,
preferedContents *[]string,
) (*nfpm.Info, error) {
pkgInfo := getBasePkgInfo(vars, input)
pkgInfo.Description = vars.Description.Resolved()
pkgInfo.Platform = "linux"
pkgInfo.Homepage = vars.Homepage.Resolved()
pkgInfo.License = strings.Join(vars.Licenses, ", ")
pkgInfo.Maintainer = vars.Maintainer.Resolved()
pkgInfo.Overridables = nfpm.Overridables{
Conflicts: append(vars.Conflicts, vars.Name),
Replaces: vars.Replaces,
Provides: append(vars.Provides, vars.Name),
Depends: deps,
}
pkgInfo.Section = vars.Group.Resolved()
pkgFormat := input.PkgFormat()
info := input.OSRelease()
if pkgFormat == "apk" {
// Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы
pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool {
return s == pkgInfo.Name
})
}
if pkgFormat == "rpm" {
pkgInfo.RPM.Group = vars.Group.Resolved()
if vars.Summary.Resolved() != "" {
pkgInfo.RPM.Summary = vars.Summary.Resolved()
} else {
lines := strings.SplitN(vars.Description.Resolved(), "\n", 2)
pkgInfo.RPM.Summary = lines[0]
}
}
if vars.Epoch != 0 {
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
}
setScripts(vars, pkgInfo, dirs.ScriptDir)
if slices.Contains(vars.Architectures, "all") {
pkgInfo.Arch = "all"
}
contents, err := buildContents(vars, dirs, preferedContents)
if err != nil {
return nil, err
}
normalizeContents(contents)
if vars.FireJailed.Resolved() {
contents, err = applyFirejailIntegration(vars, dirs, contents)
if err != nil {
return nil, err
}
}
pkgInfo.Overridables.Contents = contents
if len(vars.AutoProv.Resolved()) == 1 && decoder.IsTruthy(vars.AutoProv.Resolved()[0]) {
f := finddeps.New(info, pkgFormat)
err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList.Resolved())
if err != nil {
return nil, err
}
}
if len(vars.AutoReq.Resolved()) == 1 && decoder.IsTruthy(vars.AutoReq.Resolved()[0]) {
f := finddeps.New(info, pkgFormat)
err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList.Resolved())
if err != nil {
return nil, err
}
}
return pkgInfo, nil
}
func (e *LocalScriptExecutor) ExecuteFunctions(ctx context.Context, dirs types.Directories, dec *decoder.Decoder) error {
prepare, ok := dec.GetFunc("prepare")
if ok {
slog.Info(gotext.Get("Executing prepare()"))
err := prepare(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return err
}
}
build, ok := dec.GetFunc("build")
if ok {
slog.Info(gotext.Get("Executing build()"))
err := build(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return err
}
}
return nil
}
func (e *LocalScriptExecutor) ExecutePackageFunctions(
ctx context.Context,
dec *decoder.Decoder,
dirs types.Directories,
packageName string,
) (*FunctionsOutput, error) {
output := &FunctionsOutput{}
var packageFuncName string
var filesFuncName string
if packageName == "" {
packageFuncName = "package"
filesFuncName = "files"
} else {
packageFuncName = fmt.Sprintf("package_%s", packageName)
filesFuncName = fmt.Sprintf("files_%s", packageName)
}
packageFn, ok := dec.GetFunc(packageFuncName)
if ok {
slog.Info(gotext.Get("Executing %s()", packageFuncName))
err := packageFn(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return nil, err
}
}
files, ok := dec.GetFuncP(filesFuncName, func(ctx context.Context, s *interp.Runner) error {
// It should be done via interp.RunnerOption,
// but due to the issues below, it cannot be done.
// - https://github.com/mvdan/sh/issues/962
// - https://github.com/mvdan/sh/issues/1125
script, err := syntax.NewParser().Parse(strings.NewReader("cd $pkgdir && shopt -s globstar"), "")
if err != nil {
return err
}
return s.Run(ctx, script)
})
if ok {
slog.Info(gotext.Get("Executing %s()", filesFuncName))
buf := &bytes.Buffer{}
err := files(
ctx,
interp.Dir(dirs.PkgDir),
interp.StdIO(os.Stdin, buf, os.Stderr),
)
if err != nil {
return nil, err
}
contents, err := shlex.Split(buf.String())
if err != nil {
return nil, err
}
output.Contents = &contents
}
return output, nil
}

View File

@@ -1,65 +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 build
import (
"context"
"os"
"path/filepath"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type ScriptResolver struct {
cfg Config
}
type ScriptInfo struct {
Script string
Repository string
}
func (s *ScriptResolver) ResolveScript(
ctx context.Context,
pkg *alrsh.Package,
) *ScriptInfo {
var repository, script string
repodir := s.cfg.GetPaths().RepoDir
repository = pkg.Repository
// 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 {
// 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{
Repository: repository,
Script: script,
}
}

View File

@@ -1,47 +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 build
import (
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type ScriptViewerConfig interface {
PagerStyle() string
}
type ScriptViewer struct {
config ScriptViewerConfig
}
func (s *ScriptViewer) ViewScript(
ctx context.Context,
input *BuildInput,
a *alrsh.ScriptFile,
basePkg string,
) error {
return cliutils.PromptViewScript(
ctx,
a.Path(),
basePkg,
s.config.PagerStyle(),
input.opts.Interactive,
)
}

View File

@@ -1,89 +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 build
import (
"context"
"encoding/hex"
"fmt"
"os"
"strings"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache"
)
type SourceDownloader struct {
cfg Config
}
func NewSourceDownloader(cfg Config) *SourceDownloader {
return &SourceDownloader{
cfg,
}
}
func (s *SourceDownloader) DownloadSources(
ctx context.Context,
input *BuildInput,
basePkg string,
si SourcesInput,
) error {
for i, src := range si.Sources {
opts := dl.Options{
Name: fmt.Sprintf("[%d]", i),
URL: src,
Destination: getSrcDir(s.cfg, basePkg),
Progress: os.Stderr,
LocalDir: getScriptDir(input.script),
}
if !strings.EqualFold(si.Checksums[i], "SKIP") {
// Если контрольная сумма содержит двоеточие, используйте часть до двоеточия
// как алгоритм, а часть после как фактическую контрольную сумму.
// В противном случае используйте sha256 по умолчанию с целой строкой как контрольной суммой.
algo, hashData, ok := strings.Cut(si.Checksums[i], ":")
if ok {
checksum, err := hex.DecodeString(hashData)
if err != nil {
return err
}
opts.Hash = checksum
opts.HashAlgorithm = algo
} else {
checksum, err := hex.DecodeString(si.Checksums[i])
if err != nil {
return err
}
opts.Hash = checksum
}
}
// Используем временную директорию для загрузок
// dlcache.New добавит свой подкаталог "dl" внутри
opts.DlCache = dlcache.New(constants.TempDir)
err := dl.Download(ctx, opts)
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,333 +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 build
import (
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"regexp"
"runtime"
"slices"
"strconv"
"strings"
// Импортируем пакеты для поддержки различных форматов пакетов (APK, DEB, RPM и ARCH).
_ "github.com/goreleaser/nfpm/v2/apk"
_ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb"
_ "github.com/goreleaser/nfpm/v2/rpm"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/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/types"
)
// Функция prepareDirs подготавливает директории для сборки.
func prepareDirs(dirs types.Directories) error {
// Удаляем только директории источников и упаковки, не трогаем файлы пакетов в BaseDir
err := os.RemoveAll(dirs.SrcDir)
if err != nil {
slog.Debug("Failed to remove src directory", "path", dirs.SrcDir, "error", err)
}
err = os.RemoveAll(dirs.PkgDir)
if err != nil {
slog.Debug("Failed to remove pkg directory", "path", dirs.PkgDir, "error", err)
}
// Создаем базовую директорию для пакета с setgid битом
err = utils.EnsureTempDirWithRootOwner(dirs.BaseDir, 0o2775)
if err != nil {
return err
}
// Создаем директории с правильным владельцем для /tmp/alr с setgid битом
err = utils.EnsureTempDirWithRootOwner(dirs.SrcDir, 0o2775)
if err != nil {
return err
}
// Создаем директорию для пакетов с setgid битом
return utils.EnsureTempDirWithRootOwner(dirs.PkgDir, 0o2775)
}
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
// которые будут включены в конечный пакет.
func buildContents(vars *alrsh.Package, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
contents := []*files.Content{}
processPath := func(path, trimmed string, prefered bool) error {
fi, err := os.Lstat(path)
if err != nil {
return err
}
if fi.IsDir() {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
if !prefered {
_, err = f.Readdirnames(1)
if err != io.EOF {
return nil
}
}
contents = append(contents, &files.Content{
Source: path,
Destination: trimmed,
Type: "dir",
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
},
})
return nil
}
if fi.Mode()&os.ModeSymlink != 0 {
link, err := os.Readlink(path)
if err != nil {
return err
}
link = strings.TrimPrefix(link, dirs.PkgDir)
contents = append(contents, &files.Content{
Source: link,
Destination: trimmed,
Type: "symlink",
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
},
})
return nil
}
fileContent := &files.Content{
Source: path,
Destination: trimmed,
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
Size: fi.Size(),
},
}
if slices.Contains(vars.Backup.Resolved(), trimmed) {
fileContent.Type = "config|noreplace"
}
contents = append(contents, fileContent)
return nil
}
if preferedContents != nil {
for _, trimmed := range *preferedContents {
path := filepath.Join(dirs.PkgDir, trimmed)
if err := processPath(path, trimmed, true); err != nil {
return nil, err
}
}
} else {
err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
trimmed := strings.TrimPrefix(path, dirs.PkgDir)
return processPath(path, trimmed, false)
})
if err != nil {
return nil, err
}
}
return contents, nil
}
func normalizeContents(contents []*files.Content) {
for _, content := range contents {
content.Destination = filepath.Join("/", content.Destination)
}
}
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+(?P<repo>.+)$`)
func getBasePkgInfo(vars *alrsh.Package, input interface {
RepositoryProvider
OsInfoProvider
},
) *nfpm.Info {
repo := input.Repository()
return &nfpm.Info{
Name: fmt.Sprintf("%s+%s", vars.Name, repo),
Arch: cpu.Arch(),
Version: vars.Version,
Release: overrides.ReleasePlatformSpecific(vars.Release, input.OSRelease()),
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
}
}
// Функция getPkgFormat возвращает формат пакета из менеджера пакетов,
// или ALR_PKG_FORMAT, если он установлен.
func GetPkgFormat(mgr manager.Manager) string {
pkgFormat := mgr.Format()
if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok {
pkgFormat = format
}
return pkgFormat
}
// Функция createBuildEnvVars создает переменные окружения, которые будут установлены
// в скрипте сборки при его выполнении.
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
}
// Функция setScripts добавляет скрипты-перехватчики к метаданным пакета.
func setScripts(vars *alrsh.Package, info *nfpm.Info, scriptDir string) {
if vars.Scripts.Resolved().PreInstall != "" {
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PreInstall)
}
if vars.Scripts.Resolved().PostInstall != "" {
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PostInstall)
}
if vars.Scripts.Resolved().PreRemove != "" {
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PreRemove)
}
if vars.Scripts.Resolved().PostRemove != "" {
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PostRemove)
}
if vars.Scripts.Resolved().PreUpgrade != "" {
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade)
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade)
}
if vars.Scripts.Resolved().PostUpgrade != "" {
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade)
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade)
}
if vars.Scripts.Resolved().PreTrans != "" {
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PreTrans)
}
if vars.Scripts.Resolved().PostTrans != "" {
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PostTrans)
}
}
/*
// Функция setVersion изменяет переменную версии в скрипте runner.
// Она используется для установки версии на вывод функции version().
func setVersion(ctx context.Context, r *interp.Runner, to string) error {
fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "")
if err != nil {
return err
}
return r.Run(ctx, fl)
}
*/
// Функция packageNames возвращает имена всех предоставленных пакетов.
/*
func packageNames(pkgs []db.Package) []string {
names := make([]string, len(pkgs))
for i, p := range pkgs {
names[i] = p.Name
}
return names
}
*/
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
func removeDuplicates[T comparable](slice []T) []T {
seen := map[T]struct{}{}
result := []T{}
for _, item := range slice {
if _, ok := seen[item]; !ok {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}
func removeDuplicatesSources(sources, checksums []string) ([]string, []string) {
seen := map[string]string{}
keys := make([]string, 0)
for i, s := range sources {
if val, ok := seen[s]; !ok || strings.EqualFold(val, "SKIP") {
if !ok {
keys = append(keys, s)
}
seen[s] = checksums[i]
}
}
newSources := make([]string, len(keys))
newChecksums := make([]string, len(keys))
for i, k := range keys {
newSources[i] = k
newChecksums[i] = seen[k]
}
return newSources, newChecksums
}

View File

@@ -1,47 +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 build
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRemoveDuplicatesSources(t *testing.T) {
type testCase struct {
Name string
Sources []string
Checksums []string
NewSources []string
NewChecksums []string
}
for _, tc := range []testCase{{
Name: "prefer non-skip values",
Sources: []string{"a", "b", "c", "a"},
Checksums: []string{"skip", "skip", "skip", "1"},
NewSources: []string{"a", "b", "c"},
NewChecksums: []string{"1", "skip", "skip"},
}} {
t.Run(tc.Name, func(t *testing.T) {
s, c := removeDuplicatesSources(tc.Sources, tc.Checksums)
assert.Equal(t, s, tc.NewSources)
assert.Equal(t, c, tc.NewChecksums)
})
}
}

View File

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

View File

@@ -1,183 +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 appbuilder
import (
"context"
"errors"
"log/slog"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
type AppDeps struct {
Cfg *config.ALRConfig
DB *db.Database
Repos *repos.Repos
Info *distro.OSRelease
Manager manager.Manager
}
func (d *AppDeps) Defer() {
if d.DB != nil {
if err := d.DB.Close(); err != nil {
slog.Warn("failed to close db", "err", err)
}
}
}
type AppBuilder struct {
deps AppDeps
err error
ctx context.Context
}
func New(ctx context.Context) *AppBuilder {
return &AppBuilder{ctx: ctx}
}
func (b *AppBuilder) UseConfig(cfg *config.ALRConfig) *AppBuilder {
if b.err != nil {
return b
}
b.deps.Cfg = cfg
return b
}
func (b *AppBuilder) WithConfig() *AppBuilder {
if b.err != nil {
return b
}
cfg := config.New()
if err := cfg.Load(); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
return b
}
b.deps.Cfg = cfg
return b
}
func (b *AppBuilder) WithDB() *AppBuilder {
if b.err != nil {
return b
}
cfg := b.deps.Cfg
if cfg == nil {
b.err = errors.New("config is required before initializing DB")
return b
}
db := db.New(cfg)
if err := db.Init(b.ctx); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error initialization database"), err)
return b
}
b.deps.DB = db
return b
}
func (b *AppBuilder) WithRepos() *AppBuilder {
b.withRepos(true, false)
return b
}
func (b *AppBuilder) WithReposForcePull() *AppBuilder {
b.withRepos(true, true)
return b
}
func (b *AppBuilder) WithReposNoPull() *AppBuilder {
b.withRepos(false, false)
return b
}
func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder {
if b.err != nil {
return b
}
cfg := b.deps.Cfg
db := b.deps.DB
info := b.deps.Info
if info == nil {
b.WithDistroInfo()
info = b.deps.Info
}
if cfg == nil || db == nil || info == nil {
b.err = errors.New("config, db and info are required before initializing repos")
return b
}
rs := repos.New(cfg, db)
if enablePull && (forcePull || cfg.AutoPull()) {
if err := rs.Pull(b.ctx, cfg.Repos()); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error pulling repositories"), err)
return b
}
}
b.deps.Repos = rs
return b
}
func (b *AppBuilder) WithDistroInfo() *AppBuilder {
if b.err != nil {
return b
}
b.deps.Info, b.err = distro.ParseOSRelease(b.ctx)
if b.err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error parsing os release"), b.err)
}
return b
}
func (b *AppBuilder) WithManager() *AppBuilder {
if b.err != nil {
return b
}
b.deps.Manager = manager.Detect()
if b.deps.Manager == nil {
b.err = cliutils.FormatCliExit(gotext.Get("Unable to detect a supported package manager on the system"), nil)
}
return b
}
func (b *AppBuilder) Build() (*AppDeps, error) {
if b.err != nil {
return nil, b.err
}
return &b.deps, nil
}

View File

@@ -1,35 +1,35 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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 cliutils package cliutils
import ( import (
"context" "context"
"log/slog"
"os" "os"
"strings" "strings"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager" "plemya-x.ru/alr/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" "plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/internal/pager"
"plemya-x.ru/alr/internal/translations"
"plemya-x.ru/alr/pkg/loggerctx"
) )
// 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
@@ -38,7 +38,7 @@ func YesNoPrompt(ctx context.Context, msg string, interactive, def bool) (bool,
var answer bool var answer bool
err := survey.AskOne( err := survey.AskOne(
&survey.Confirm{ &survey.Confirm{
Message: msg, Message: translations.Translator(ctx).TranslateTo(msg, config.Language(ctx)),
Default: def, Default: def,
}, },
&answer, &answer,
@@ -53,11 +53,14 @@ func YesNoPrompt(ctx context.Context, msg string, interactive, def bool) (bool,
// shows it if they answer yes, then asks if they'd still like to // shows it if they answer yes, then asks if they'd still like to
// continue, and exits if they answer no. // continue, and exits if they answer no.
func PromptViewScript(ctx context.Context, script, name, style string, interactive bool) error { func PromptViewScript(ctx context.Context, script, name, style string, interactive bool) error {
log := loggerctx.From(ctx)
if !interactive { if !interactive {
return nil return nil
} }
view, err := YesNoPrompt(ctx, gotext.Get("Would you like to view the build script for %s", name), interactive, false) scriptPrompt := translations.Translator(ctx).TranslateTo("Would you like to view the build script for", config.Language(ctx)) + " " + name
view, err := YesNoPrompt(ctx, scriptPrompt, interactive, false)
if err != nil { if err != nil {
return err return err
} }
@@ -68,14 +71,13 @@ func PromptViewScript(ctx context.Context, script, name, style string, interacti
return err return err
} }
cont, err := YesNoPrompt(ctx, gotext.Get("Would you still like to continue?"), interactive, false) cont, err := YesNoPrompt(ctx, "Would you still like to continue?", interactive, false)
if err != nil { if err != nil {
return err return err
} }
if !cont { if !cont {
slog.Error(gotext.Get("User chose not to continue after reading script")) log.Fatal(translations.Translator(ctx).TranslateTo("User chose not to continue after reading script", config.Language(ctx))).Send()
os.Exit(1)
} }
} }
@@ -102,65 +104,25 @@ 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][]alrsh.Package, verb string, interactive bool) []alrsh.Package { func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string, interactive bool) []db.Package {
return FlattenPkgsWithContext(ctx, found, verb, interactive, false) log := loggerctx.From(ctx)
} var outPkgs []db.Package
// FlattenPkgsWithContext расширенная версия FlattenPkgs с контекстом обработки зависимостей
func FlattenPkgsWithContext(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool, isDependency bool) []alrsh.Package {
var outPkgs []alrsh.Package
for _, pkgs := range found { for _, pkgs := range found {
if len(pkgs) > 1 { if len(pkgs) > 1 && interactive {
// Проверяем, являются ли пакеты подпакетами одного мультипакета choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
if isMultiPackage(pkgs) && verb == "install" { if err != nil {
// Для мультипакетов при установке ВСЕГДА берем все подпакеты без выбора log.Fatal("Error prompting for choice of package").Send()
// Это правильное поведение как для прямой установки, так и для зависимостей
outPkgs = append(outPkgs, pkgs...)
} else if interactive {
// Для разных пакетов с одинаковым именем - показываем меню выбора
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
if err != nil {
slog.Error(gotext.Get("Error prompting for choice of package"))
os.Exit(1)
}
outPkgs = append(outPkgs, choice)
} else {
// Если не интерактивный режим - берем первый
outPkgs = append(outPkgs, pkgs[0])
} }
} else { outPkgs = append(outPkgs, choice)
// Если только один пакет - берем его } else if len(pkgs) == 1 || !interactive {
outPkgs = append(outPkgs, pkgs[0]) outPkgs = append(outPkgs, pkgs[0])
} }
} }
return outPkgs return outPkgs
} }
// isMultiPackage проверяет, являются ли пакеты подпакетами одного мультипакета
func isMultiPackage(pkgs []alrsh.Package) bool {
if len(pkgs) <= 1 {
return false
}
// Проверяем, что у всех пакетов одинаковый BasePkgName и Repository
firstBasePkg := pkgs[0].BasePkgName
firstRepo := pkgs[0].Repository
if firstBasePkg == "" {
return false // Не мультипакет
}
for _, pkg := range pkgs[1:] {
if pkg.BasePkgName != firstBasePkg || pkg.Repository != firstRepo {
return false
}
}
return true
}
// PkgPrompt asks the user to choose between multiple packages. // PkgPrompt asks the user to choose between multiple packages.
func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, interactive bool) (alrsh.Package, error) { func PkgPrompt(ctx context.Context, options []db.Package, verb string, interactive bool) (db.Package, error) {
if !interactive { if !interactive {
return options[0], nil return options[0], nil
} }
@@ -172,13 +134,13 @@ func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, intera
prompt := &survey.Select{ prompt := &survey.Select{
Options: names, Options: names,
Message: gotext.Get("Choose which package to %s", verb), Message: translations.Translator(ctx).TranslateTo("Choose which package to "+verb, config.Language(ctx)),
} }
var choice int var choice int
err := survey.AskOne(prompt, &choice) err := survey.AskOne(prompt, &choice)
if err != nil { if err != nil {
return alrsh.Package{}, err return db.Package{}, err
} }
return options[choice], nil return options[choice], nil
@@ -193,7 +155,7 @@ func ChooseOptDepends(ctx context.Context, options []string, verb string, intera
prompt := &survey.MultiSelect{ prompt := &survey.MultiSelect{
Options: options, Options: options,
Message: gotext.Get("Choose which optional package(s) to install"), Message: translations.Translator(ctx).TranslateTo("Choose which optional package(s) to install", config.Language(ctx)),
} }
var choices []int var choices []int

View File

@@ -1,102 +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 cliutils
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
// Templates are based on https://github.com/urfave/cli/blob/3b17080d70a630feadadd23dd036cad121dd9a50/template.go
//nolint:unused
var (
helpNameTemplate = `{{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}`
descriptionTemplate = `{{wrap .Description 3}}`
authorsTemplate = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}`
visibleCommandTemplate = `{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}`
visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}`
visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}}
{{if .Name}}{{.Name}}
{{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}}
{{else}}{{$e}}
{{end}}{{end}}{{end}}`
visibleFlagTemplate = `{{range $i, $e := .VisibleFlags}}
{{wrap $e.String 6}}{{end}}`
copyrightTemplate = `{{wrap .Copyright 3}}`
)
func GetAppCliTemplate() string {
return fmt.Sprintf(`%s:
{{template "helpNameTemplate" .}}
%s:
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[%s]{{end}}{{if .Commands}} %s [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
%s:
{{.Version}}{{end}}{{end}}{{if .Description}}
%s:
{{template "descriptionTemplate" .}}{{end}}
{{- if len .Authors}}
%s{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}}
%s:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
%s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
%s:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}}
%s:
{{template "copyrightTemplate" .}}{{end}}
`, gotext.Get("NAME"), gotext.Get("USAGE"), gotext.Get("global options"), gotext.Get("command"), gotext.Get("command options"), gotext.Get("arguments"), gotext.Get("VERSION"), gotext.Get("DESCRIPTION"), gotext.Get("AUTHOR"), gotext.Get("COMMANDS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("COPYRIGHT"))
}
func GetCommandHelpTemplate() string {
return fmt.Sprintf(`%s:
{{template "helpNameTemplate" .}}
%s:
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Category}}
%s:
{{.Category}}{{end}}{{if .Description}}
%s:
{{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
%s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
%s:{{template "visibleFlagTemplate" .}}{{end}}
`, gotext.Get("NAME"),
gotext.Get("USAGE"),
gotext.Get("command options"),
gotext.Get("arguments"),
gotext.Get("CATEGORY"),
gotext.Get("DESCRIPTION"),
gotext.Get("OPTIONS"),
gotext.Get("OPTIONS"),
)
}

View File

@@ -1,72 +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 cliutils
import (
"errors"
"fmt"
"log/slog"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
)
type BashCompleteWithErrorFunc func(c *cli.Context) error
func BashCompleteWithError(f BashCompleteWithErrorFunc) cli.BashCompleteFunc {
return func(c *cli.Context) { HandleExitCoder(f(c)) }
}
func HandleExitCoder(err error) {
if err == nil {
return
}
if exitErr, ok := err.(cli.ExitCoder); ok {
if err.Error() != "" {
if _, ok := exitErr.(cli.ErrorFormatter); ok {
slog.Error(fmt.Sprintf("%+v\n", err))
} else {
slog.Error(err.Error())
}
}
cli.OsExiter(exitErr.ExitCode())
return
}
slog.Error(err.Error())
cli.OsExiter(1)
}
func FormatCliExit(msg string, err error) cli.ExitCoder {
return FormatCliExitWithCode(msg, err, 1)
}
func FormatCliExitWithCode(msg string, err error, exitCode int) cli.ExitCoder {
if err == nil {
return cli.Exit(errors.New(msg), exitCode)
}
return cli.Exit(fmt.Errorf("%s: %w", msg, err), exitCode)
}
func WarnLegacyCommand(newSyntax string) {
slog.Warn(
gotext.Get(
"This command is deprecated and would be removed in the future, use \"%s\" instead!", newSyntax,
),
)
}

View File

@@ -1,265 +1,80 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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 config package config
import ( import (
"fmt" "context"
"os" "os"
"os/exec" "sync"
"path/filepath"
"github.com/goccy/go-yaml" "github.com/pelletier/go-toml/v2"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/v2"
ktoml "github.com/knadh/koanf/parsers/toml/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" "plemya-x.ru/alr/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" "plemya-x.ru/alr/pkg/loggerctx"
) )
type ALRConfig struct { var defaultConfig = &types.Config{
cfg *types.Config RootCmd: "sudo",
paths *Paths PagerStyle: "native",
IgnorePkgUpdates: []string{},
System *SystemConfig Repos: []types.Repo{
env *EnvConfig {
} Name: "default",
URL: "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git",
func New() *ALRConfig {
return &ALRConfig{
System: NewSystemConfig(),
env: NewEnvConfig(),
}
}
func defaultConfigKoanf() *koanf.Koanf {
k := koanf.New(".")
defaults := map[string]interface{}{
"rootCmd": "sudo",
"useRootCmd": true,
"pagerStyle": "native",
"ignorePkgUpdates": []string{},
"logLevel": "info",
"autoPull": true,
"updateSystemOnUpgrade": false,
"repos": []types.Repo{
{
Name: "alr-default",
URL: "https://gitea.plemya-x.ru/Plemya-x/alr-default.git",
},
}, },
} },
if err := k.Load(confmap.Provider(defaults, "."), nil); err != nil {
panic(k)
}
return k
} }
func (c *ALRConfig) Load() error { var (
config := types.Config{} configMtx sync.Mutex
config *types.Config
)
merged := koanf.New(".") // Config returns a ALR configuration struct.
// The first time it's called, it'll load the config from a file.
// Subsequent calls will just return the same value.
func Config(ctx context.Context) *types.Config {
configMtx.Lock()
defer configMtx.Unlock()
log := loggerctx.From(ctx)
if err := c.System.Load(); err != nil { if config == nil {
return fmt.Errorf("failed to load system config: %w", err) cfgFl, err := os.Open(GetPaths(ctx).ConfigPath)
} if err != nil {
log.Warn("Error opening config file, using defaults").Err(err).Send()
return defaultConfig
}
defer cfgFl.Close()
if err := c.env.Load(); err != nil { // Copy the default configuration into config
return fmt.Errorf("failed to load env config: %w", err) defCopy := *defaultConfig
} config = &defCopy
config.Repos = nil
systemK := c.System.koanf() err = toml.NewDecoder(cfgFl).Decode(config)
envK := c.env.koanf() if err != nil {
log.Warn("Error decoding config file, using defaults").Err(err).Send()
if err := merged.Merge(defaultConfigKoanf()); err != nil { // Set config back to nil so that we try again next time
return fmt.Errorf("failed to merge default config: %w", err) config = nil
} return defaultConfig
if err := merged.Merge(systemK); err != nil {
return fmt.Errorf("failed to merge system config: %w", err)
}
if err := merged.Merge(envK); err != nil {
return fmt.Errorf("failed to merge env config: %w", err)
}
if err := merged.Unmarshal("", &config); err != nil {
return fmt.Errorf("failed to unmarshal merged config: %w", err)
}
c.cfg = &config
c.paths = &Paths{}
c.paths.UserConfigPath = constants.SystemConfigPath
c.paths.CacheDir = constants.SystemCachePath
c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo")
c.paths.PkgsDir = filepath.Join(constants.TempDir, "pkgs") // Перемещаем в /tmp/alr/pkgs
c.paths.DBPath = filepath.Join(c.paths.CacheDir, "alr.db")
// Проверяем существование кэш-директории, но не пытаемся создать
if _, err := os.Stat(c.paths.CacheDir); err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("failed to check cache directory: %w", err)
} }
} }
// Выполняем миграцию конфигурации при необходимости return config
if err := c.migrateConfig(); err != nil {
return fmt.Errorf("failed to migrate config: %w", err)
}
return nil
} }
func (c *ALRConfig) ToYAML() (string, error) {
data, err := yaml.Marshal(c.cfg)
if err != nil {
return "", err
}
return string(data), nil
}
func (c *ALRConfig) migrateConfig() error {
// Проверяем, существует ли конфигурационный файл
if _, err := os.Stat(constants.SystemConfigPath); os.IsNotExist(err) {
// Если файла нет, создаем полный конфигурационный файл с дефолтными значениями
if err := c.createDefaultConfig(); err != nil {
// Если не удается создать конфиг, это не критично - продолжаем работу
// но выводим предупреждение
fmt.Fprintf(os.Stderr, "Предупреждение: не удалось создать конфигурационный файл %s: %v\n", constants.SystemConfigPath, err)
return nil
}
} else {
// Если файл существует, проверяем, есть ли в нем новая опция
if !c.System.k.Exists("updateSystemOnUpgrade") {
// Если опции нет, добавляем ее со значением по умолчанию
c.System.SetUpdateSystemOnUpgrade(false)
// Сохраняем обновленную конфигурацию
if err := c.System.Save(); err != nil {
// Если не удается сохранить - это не критично, продолжаем работу
return nil
}
}
}
return nil
}
func (c *ALRConfig) createDefaultConfig() error {
// Проверяем, запущен ли процесс от root
if os.Getuid() != 0 {
// Если не root, пытаемся запустить создание конфига с повышением привилегий
return c.createDefaultConfigWithPrivileges()
}
// Если уже root, создаем конфиг напрямую
return c.doCreateDefaultConfig()
}
func (c *ALRConfig) createDefaultConfigWithPrivileges() error {
// Если useRootCmd отключен, просто пытаемся создать без повышения привилегий
if !c.cfg.UseRootCmd {
return c.doCreateDefaultConfig()
}
// Определяем команду для повышения привилегий
rootCmd := c.cfg.RootCmd
if rootCmd == "" {
rootCmd = "sudo" // fallback
}
// Создаем временный файл с дефолтной конфигурацией
tmpFile, err := os.CreateTemp("", "alr-config-*.toml")
if err != nil {
return fmt.Errorf("не удалось создать временный файл: %w", err)
}
defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
// Генерируем дефолтную конфигурацию во временный файл
defaults := defaultConfigKoanf()
tempSystemConfig := &SystemConfig{k: defaults}
bytes, err := tempSystemConfig.k.Marshal(ktoml.Parser())
if err != nil {
return fmt.Errorf("не удалось сериализовать конфигурацию: %w", err)
}
if _, err := tmpFile.Write(bytes); err != nil {
return fmt.Errorf("не удалось записать во временный файл: %w", err)
}
tmpFile.Close()
// Используем команду повышения привилегий для создания директории и копирования файла
// Создаем директорию с правами
configDir := filepath.Dir(constants.SystemConfigPath)
mkdirCmd := exec.Command(rootCmd, "mkdir", "-p", configDir)
if err := mkdirCmd.Run(); err != nil {
return fmt.Errorf("не удалось создать директорию %s: %w", configDir, err)
}
// Копируем файл в нужное место
cpCmd := exec.Command(rootCmd, "cp", tmpFile.Name(), constants.SystemConfigPath)
if err := cpCmd.Run(); err != nil {
return fmt.Errorf("не удалось скопировать конфигурацию в %s: %w", constants.SystemConfigPath, err)
}
// Устанавливаем правильные права доступа
chmodCmd := exec.Command(rootCmd, "chmod", "644", constants.SystemConfigPath)
if err := chmodCmd.Run(); err != nil {
// Не критично, продолжаем
fmt.Fprintf(os.Stderr, "Предупреждение: не удалось установить права доступа для %s: %v\n", constants.SystemConfigPath, err)
}
return nil
}
func (c *ALRConfig) doCreateDefaultConfig() error {
// Проверяем, существует ли директория для конфига
configDir := filepath.Dir(constants.SystemConfigPath)
if _, err := os.Stat(configDir); os.IsNotExist(err) {
// Пытаемся создать директорию
if err := os.MkdirAll(configDir, 0755); err != nil {
return fmt.Errorf("не удалось создать директорию %s: %w", configDir, err)
}
}
// Загружаем дефолтную конфигурацию
defaults := defaultConfigKoanf()
// Копируем все дефолтные значения в системную конфигурацию
c.System.k = defaults
// Сохраняем конфигурацию в файл
if err := c.System.Save(); err != nil {
return fmt.Errorf("не удалось сохранить конфигурацию в %s: %w", constants.SystemConfigPath, err)
}
return nil
}
func (c *ALRConfig) RootCmd() string { return c.cfg.RootCmd }
func (c *ALRConfig) PagerStyle() string { return c.cfg.PagerStyle }
func (c *ALRConfig) AutoPull() bool { return c.cfg.AutoPull }
func (c *ALRConfig) Repos() []types.Repo { return c.cfg.Repos }
func (c *ALRConfig) SetRepos(repos []types.Repo) { c.System.SetRepos(repos) }
func (c *ALRConfig) IgnorePkgUpdates() []string { return c.cfg.IgnorePkgUpdates }
func (c *ALRConfig) LogLevel() string { return c.cfg.LogLevel }
func (c *ALRConfig) UseRootCmd() bool { return c.cfg.UseRootCmd }
func (c *ALRConfig) UpdateSystemOnUpgrade() bool { return c.cfg.UpdateSystemOnUpgrade }
func (c *ALRConfig) GetPaths() *Paths { return c.paths }

View File

@@ -1,76 +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 config
import (
"strings"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/v2"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
type EnvConfig struct {
k *koanf.Koanf
}
func NewEnvConfig() *EnvConfig {
return &EnvConfig{
k: koanf.New("."),
}
}
func (c *EnvConfig) koanf() *koanf.Koanf {
return c.k
}
func (c *EnvConfig) Load() error {
allowedKeys := map[string]struct{}{
"ALR_LOG_LEVEL": {},
"ALR_PAGER_STYLE": {},
"ALR_AUTO_PULL": {},
}
err := c.k.Load(env.Provider("ALR_", ".", func(s string) string {
_, ok := allowedKeys[s]
if !ok {
return ""
}
withoutPrefix := strings.TrimPrefix(s, "ALR_")
lowered := strings.ToLower(withoutPrefix)
dotted := strings.ReplaceAll(lowered, "__", ".")
parts := strings.Split(dotted, ".")
for i, part := range parts {
if strings.Contains(part, "_") {
parts[i] = toCamelCase(part)
}
}
return strings.Join(parts, ".")
}), nil)
return err
}
func toCamelCase(s string) string {
parts := strings.Split(s, "_")
for i := 1; i < len(parts); i++ {
if len(parts[i]) > 0 {
parts[i] = cases.Title(language.Und, cases.NoLower).String(parts[i])
}
}
return strings.Join(parts, "")
}

68
internal/config/lang.go Normal file
View File

@@ -0,0 +1,68 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* 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 config
import (
"context"
"os"
"strings"
"sync"
"golang.org/x/text/language"
"plemya-x.ru/alr/pkg/loggerctx"
)
var (
langMtx sync.Mutex
lang language.Tag
langSet bool
)
// Language returns the system language.
// The first time it's called, it'll detect the langauge based on
// the $LANG environment variable.
// Subsequent calls will just return the same value.
func Language(ctx context.Context) language.Tag {
langMtx.Lock()
defer langMtx.Unlock()
log := loggerctx.From(ctx)
if !langSet {
syslang := SystemLang()
tag, err := language.Parse(syslang)
if err != nil {
log.Fatal("Error parsing system language").Err(err).Send()
}
base, _ := tag.Base()
lang = language.Make(base.String())
langSet = true
}
return lang
}
// SystemLang returns the system language based on
// the $LANG environment variable.
func SystemLang() string {
lang := os.Getenv("LANG")
lang, _, _ = strings.Cut(lang, ".")
if lang == "" || lang == "C" {
lang = "en"
}
return lang
}

View File

@@ -1,30 +1,109 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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 config package config
import (
"context"
"os"
"path/filepath"
"sync"
"github.com/pelletier/go-toml/v2"
"plemya-x.ru/alr/pkg/loggerctx"
)
// Paths contains various paths used by ALR // Paths contains various paths used by ALR
type Paths struct { type Paths struct {
SystemConfigPath string ConfigDir string
UserConfigPath string ConfigPath string
CacheDir string CacheDir string
RepoDir string RepoDir string
PkgsDir string PkgsDir string
DBPath string DBPath string
}
var (
pathsMtx sync.Mutex
paths *Paths
)
// GetPaths returns a Paths struct.
// The first time it's called, it'll generate the struct
// using information from the system.
// Subsequent calls will return the same value.
func GetPaths(ctx context.Context) *Paths {
pathsMtx.Lock()
defer pathsMtx.Unlock()
log := loggerctx.From(ctx)
if paths == nil {
paths = &Paths{}
cfgDir, err := os.UserConfigDir()
if err != nil {
log.Fatal("Unable to detect user config directory").Err(err).Send()
}
paths.ConfigDir = filepath.Join(cfgDir, "alr")
err = os.MkdirAll(paths.ConfigDir, 0o755)
if err != nil {
log.Fatal("Unable to create ALR config directory").Err(err).Send()
}
paths.ConfigPath = filepath.Join(paths.ConfigDir, "alr.toml")
if _, err := os.Stat(paths.ConfigPath); err != nil {
cfgFl, err := os.Create(paths.ConfigPath)
if err != nil {
log.Fatal("Unable to create ALR config file").Err(err).Send()
}
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
if err != nil {
log.Fatal("Error encoding default configuration").Err(err).Send()
}
cfgFl.Close()
}
cacheDir, err := os.UserCacheDir()
if err != nil {
log.Fatal("Unable to detect cache directory").Err(err).Send()
}
paths.CacheDir = filepath.Join(cacheDir, "alr")
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
err = os.MkdirAll(paths.RepoDir, 0o755)
if err != nil {
log.Fatal("Unable to create repo cache directory").Err(err).Send()
}
err = os.MkdirAll(paths.PkgsDir, 0o755)
if err != nil {
log.Fatal("Unable to create package cache directory").Err(err).Send()
}
paths.DBPath = filepath.Join(paths.CacheDir, "db")
}
return paths
} }

View File

@@ -1,151 +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 config
import (
"encoding/json"
"errors"
"fmt"
"os"
ktoml "github.com/knadh/koanf/parsers/toml/v2"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type SystemConfig struct {
k *koanf.Koanf
cfg *types.Config
}
func NewSystemConfig() *SystemConfig {
return &SystemConfig{
k: koanf.New("."),
cfg: &types.Config{},
}
}
func (c *SystemConfig) koanf() *koanf.Koanf {
return c.k
}
func (c *SystemConfig) Load() error {
if _, err := os.Stat(constants.SystemConfigPath); errors.Is(err, os.ErrNotExist) {
return nil
}
if err := c.k.Load(file.Provider(constants.SystemConfigPath), ktoml.Parser()); err != nil {
return err
}
return c.k.Unmarshal("", c.cfg)
}
func (c *SystemConfig) Save() error {
bytes, err := c.k.Marshal(ktoml.Parser())
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
file, err := os.Create(constants.SystemConfigPath)
if err != nil {
return fmt.Errorf("failed to create config file: %w", err)
}
defer func() {
if cerr := file.Close(); cerr != nil && err == nil {
err = cerr
}
}()
if _, err := file.Write(bytes); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
if err := file.Sync(); err != nil {
return fmt.Errorf("failed to sync config: %w", err)
}
return nil
}
func (c *SystemConfig) SetRootCmd(v string) {
err := c.k.Set("rootCmd", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetUseRootCmd(v bool) {
err := c.k.Set("useRootCmd", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetPagerStyle(v string) {
err := c.k.Set("pagerStyle", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetIgnorePkgUpdates(v []string) {
err := c.k.Set("ignorePkgUpdates", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetAutoPull(v bool) {
err := c.k.Set("autoPull", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetLogLevel(v string) {
err := c.k.Set("logLevel", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetRepos(v []types.Repo) {
b, err := json.Marshal(v)
if err != nil {
panic(err)
}
var m []interface{}
err = json.Unmarshal(b, &m)
if err != nil {
panic(err)
}
err = c.k.Set("repo", m)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetUpdateSystemOnUpgrade(v bool) {
err := c.k.Set("updateSystemOnUpgrade", v)
if err != nil {
panic(err)
}
}

View File

@@ -1,22 +1,3 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// 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 config package config
// Version contains the version of ALR. If the version // Version contains the version of ALR. If the version

View File

@@ -1,25 +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 constants
const (
SystemConfigPath = "/etc/alr/alr.toml"
SystemCachePath = "/var/cache/alr"
TempDir = "/tmp/alr"
// PrivilegedGroup - устарело, используйте GetPrivilegedGroup()
PrivilegedGroup = "wheel" // оставлено для обратной совместимости
)

View File

@@ -1,21 +1,20 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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 cpu package cpu
@@ -38,12 +37,11 @@ func armVariant() string {
return armEnv return armEnv
} }
switch { if cpu.ARM.HasVFPv3 {
case cpu.ARM.HasVFPv3:
return "arm7" return "arm7"
case cpu.ARM.HasVFP: } else if cpu.ARM.HasVFP {
return "arm6" return "arm6"
default: } else {
return "arm5" return "arm5"
} }
} }

View File

@@ -1,167 +1,347 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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 package db
import ( import (
"context" "context"
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"fmt" "fmt"
"log/slog" "sync"
"os"
"path/filepath"
"github.com/leonelquinteros/gotext" "github.com/jmoiron/sqlx"
_ "modernc.org/sqlite" "golang.org/x/exp/slices"
"xorm.io/xorm" "modernc.org/sqlite"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "plemya-x.ru/alr/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" "plemya-x.ru/alr/pkg/loggerctx"
) )
const CurrentVersion = 5 // CurrentVersion is the current version of the database.
// The database is reset if its version doesn't match this.
const CurrentVersion = 2
type Version struct { func init() {
Version int `xorm:"'version'"` sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains)
} }
type Config interface { // Package is a ALR package's database representation
GetPaths() *config.Paths type Package struct {
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"`
Description JSON[map[string]string] `db:"description"`
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 Database struct { type version struct {
engine *xorm.Engine Version int `db:"version"`
config Config
} }
func New(config Config) *Database { var (
return &Database{ mu sync.Mutex
config: config, conn *sqlx.DB
closed = true
)
// DB returns the ALR database.
// The first time it's called, it opens the SQLite database file.
// Subsequent calls return the same connection.
func DB(ctx context.Context) *sqlx.DB {
log := loggerctx.From(ctx)
if conn != nil && !closed {
return getConn()
}
_, err := open(ctx, config.GetPaths(ctx).DBPath)
if err != nil {
log.Fatal("Error opening database").Err(err).Send()
}
return getConn()
}
func getConn() *sqlx.DB {
mu.Lock()
defer mu.Unlock()
return conn
}
func open(ctx context.Context, dsn string) (*sqlx.DB, error) {
db, err := sqlx.Open("sqlite", dsn)
if err != nil {
return nil, err
}
mu.Lock()
conn = db
closed = false
mu.Unlock()
err = initDB(ctx, dsn)
if err != nil {
return nil, err
}
return db, nil
}
// Close closes the database
func Close() error {
closed = true
if conn != nil {
return conn.Close()
} else {
return nil
} }
} }
func (d *Database) Connect() error { // initDB initializes the database
dsn := d.config.GetPaths().DBPath func initDB(ctx context.Context, dsn string) error {
log := loggerctx.From(ctx)
// Проверяем директорию для БД conn = conn.Unsafe()
dbDir := filepath.Dir(dsn) _, err := conn.ExecContext(ctx, `
if _, err := os.Stat(dbDir); err != nil { CREATE TABLE IF NOT EXISTS pkgs (
if os.IsNotExist(err) { name TEXT NOT NULL,
// Директория не существует - не пытаемся создать repository TEXT NOT NULL,
// Пользователь должен использовать alr fix для создания системных каталогов version TEXT NOT NULL,
return fmt.Errorf("cache directory does not exist, please run 'sudo alr fix' to create it") release INT NOT NULL,
} else { epoch INT,
return fmt.Errorf("failed to check database directory: %w", err) description TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = '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')),
engine, err := xorm.NewEngine("sqlite", dsn) licenses TEXT CHECK(licenses = 'null' OR (JSON_VALID(licenses) AND JSON_TYPE(licenses) = 'array')),
// engine.SetLogLevel(log.LOG_DEBUG) provides TEXT CHECK(provides = 'null' OR (JSON_VALID(provides) AND JSON_TYPE(provides) = 'array')),
// engine.ShowSQL(true) 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 { if err != nil {
return err return err
} }
d.engine = engine
return nil
}
func (d *Database) Init(ctx context.Context) error { ver, ok := GetVersion(ctx)
if err := d.Connect(); err != nil {
return err
}
if err := d.engine.Sync2(new(alrsh.Package), new(Version)); err != nil {
return err
}
ver, ok := d.GetVersion(ctx)
if ok && ver != CurrentVersion { if ok && ver != CurrentVersion {
slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion) log.Warn("Database version mismatch; resetting").Int("version", ver).Int("expected", CurrentVersion).Send()
if err := d.reset(); err != nil { reset(ctx)
return err return initDB(ctx, dsn)
}
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.")) log.Warn("Database version does not exist. Run alr fix if something isn't working.").Send()
return d.addVersion(CurrentVersion) return addVersion(ctx, CurrentVersion)
} }
return nil return nil
} }
func (d *Database) GetVersion(ctx context.Context) (int, bool) { // reset drops all the database tables
var v Version func reset(ctx context.Context) error {
has, err := d.engine.Get(&v) _, err := DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;")
if err != nil || !has { if err != nil {
return 0, false return err
} }
return v.Version, true _, err = DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS alr_db_version;")
}
func (d *Database) addVersion(ver int) error {
_, err := d.engine.Insert(&Version{Version: ver})
return err return err
} }
func (d *Database) reset() error { // IsEmpty returns true if the database has no packages in it, otherwise it returns false.
return d.engine.DropTables(new(alrsh.Package), new(Version)) func IsEmpty(ctx context.Context) bool {
var count int
err := DB(ctx).GetContext(ctx, &count, "SELECT count(1) FROM pkgs;")
if err != nil {
return true
}
return count == 0
} }
func (d *Database) InsertPackage(ctx context.Context, pkg alrsh.Package) error { // GetVersion returns the database version and a boolean indicating
session := d.engine.Context(ctx) // whether the database contained a version number
func GetVersion(ctx context.Context) (int, bool) {
affected, err := session.Where("name = ? AND repository = ?", pkg.Name, pkg.Repository).Update(&pkg) var ver version
err := DB(ctx).GetContext(ctx, &ver, "SELECT * FROM alr_db_version LIMIT 1;")
if err != nil { if err != nil {
return err return 0, false
}
return ver.Version, true
}
func addVersion(ctx context.Context, ver int) error {
_, err := DB(ctx).ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver)
return err
}
// InsertPackage adds a package to the database
func InsertPackage(ctx context.Context, pkg Package) error {
_, err := DB(ctx).NamedExecContext(ctx, `
INSERT OR REPLACE INTO pkgs (
name,
repository,
version,
release,
epoch,
description,
homepage,
maintainer,
architectures,
licenses,
provides,
conflicts,
replaces,
depends,
builddepends,
optdepends
) VALUES (
:name,
:repository,
:version,
:release,
:epoch,
:description,
:homepage,
:maintainer,
:architectures,
:licenses,
:provides,
:conflicts,
:replaces,
:depends,
:builddepends,
:optdepends
);
`, pkg)
return err
}
// GetPkgs returns a result containing packages that match the where conditions
func GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) {
stream, err := DB(ctx).QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...)
if err != nil {
return nil, err
}
return stream, nil
}
// GetPkg returns a single package that matches the where conditions
func GetPkg(ctx context.Context, where string, args ...any) (*Package, error) {
out := &Package{}
err := DB(ctx).GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
return out, err
}
// DeletePkgs deletes all packages matching the where conditions
func DeletePkgs(ctx context.Context, where string, args ...any) error {
_, err := DB(ctx).ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...)
return err
}
// jsonArrayContains is an SQLite function that checks if a JSON array
// in the database contains a given value
func jsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
value, ok := args[0].(string)
if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
} }
if affected == 0 { item, ok := args[1].(string)
_, err = session.Insert(&pkg) if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
}
var array []string
err := json.Unmarshal([]byte(value), &array)
if err != nil {
return nil, err
}
return slices.Contains(array, item), nil
}
// 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 { if err != nil {
return err 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 return nil
} }
func (d *Database) GetPkgs(_ context.Context, where string, args ...any) ([]alrsh.Package, error) { func (s JSON[T]) Value() (driver.Value, error) {
var pkgs []alrsh.Package data, err := json.Marshal(s.Val)
err := d.engine.Where(where, args...).Find(&pkgs) if err != nil {
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 &pkg, nil return string(data), nil
} }
func (d *Database) DeletePkgs(_ context.Context, where string, args ...any) error { func (s JSON[T]) MarshalYAML() (any, error) {
_, err := d.engine.Where(where, args...).Delete(&alrsh.Package{}) return s.Val, nil
return err
} }
func (d *Database) IsEmpty() bool { func (s JSON[T]) String() string {
count, err := d.engine.Count(new(alrsh.Package)) return fmt.Sprint(s.Val)
return err != nil || count == 0
} }
func (d *Database) Close() error { func (s JSON[T]) GoString() string {
return d.engine.Close() return fmt.Sprintf("%#v", s.Val)
} }

View File

@@ -1,91 +1,76 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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_test package db_test
import ( import (
"context"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/jmoiron/sqlx"
"plemya-x.ru/alr/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
) )
type TestALRConfig struct{} var testPkg = db.Package{
func (c *TestALRConfig) GetPaths() *config.Paths {
return &config.Paths{
DBPath: ":memory:",
}
}
func prepareDb() *db.Database {
database := db.New(&TestALRConfig{})
database.Init(context.Background())
return database
}
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: alrsh.OverridableFromMap(map[string]string{ Description: db.NewJSON(map[string]string{
"en": "Test package", "en": "Test package",
"ru": "Проверочный пакет", "ru": "Проверочный пакет",
}), }),
Homepage: alrsh.OverridableFromMap(map[string]string{ Homepage: db.NewJSON(map[string]string{
"en": "https://gitea.plemya-x.ru/xpamych/ALR", "en": "https://gitea.plemya-x.ru/xpamych/ALR",
}), }),
Maintainer: alrsh.OverridableFromMap(map[string]string{ Maintainer: db.NewJSON(map[string]string{
"en": "Evgeniy Khramov <xpamych@yandex.ru>", "en": "Evgeniy Khramov <xpamych@yandex.ru>",
"ru": "Евгений Храмов <xpamych@yandex.ru>", "ru": "Евгений Храмов <xpamych@yandex.ru>",
}), }),
Architectures: []string{"arm64", "amd64"}, Architectures: db.NewJSON([]string{"arm64", "amd64"}),
Licenses: []string{"GPL-3.0-or-later"}, Licenses: db.NewJSON([]string{"GPL-3.0-or-later"}),
Provides: []string{"test"}, Provides: db.NewJSON([]string{"test"}),
Conflicts: []string{"test"}, Conflicts: db.NewJSON([]string{"test"}),
Replaces: []string{"test-old"}, Replaces: db.NewJSON([]string{"test-old"}),
Depends: alrsh.OverridableFromMap(map[string][]string{ Depends: db.NewJSON(map[string][]string{
"": {"sudo"}, "": {"sudo"},
}), }),
BuildDepends: alrsh.OverridableFromMap(map[string][]string{ BuildDepends: db.NewJSON(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) {
ctx := context.Background() _, err := db.Open(":memory:")
database := prepareDb() if err != nil {
defer database.Close() t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
ver, ok := database.GetVersion(ctx) _, err = db.DB().Exec("SELECT * FROM pkgs")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
ver, ok := db.GetVersion()
if !ok { if !ok {
t.Errorf("Expected version to be present") t.Errorf("Expected version to be present")
} else if ver != db.CurrentVersion { } else if ver != db.CurrentVersion {
@@ -94,53 +79,62 @@ func TestInit(t *testing.T) {
} }
func TestInsertPackage(t *testing.T) { func TestInsertPackage(t *testing.T) {
ctx := context.Background() _, err := db.Open(":memory:")
database := prepareDb() if err != nil {
defer database.Close() t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
err := database.InsertPackage(ctx, testPkg) err = db.InsertPackage(testPkg)
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
pkgs, err := database.GetPkgs(ctx, "name = 'test' AND repository = 'default'") dbPkg := db.Package{}
err = sqlx.Get(db.DB(), &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 len(pkgs) != 1 { if !reflect.DeepEqual(testPkg, dbPkg) {
t.Fatalf("Expected 1 package, got %d", len(pkgs)) t.Errorf("Expected test package to be the same as database package")
} }
assert.Equal(t, testPkg, pkgs[0])
} }
func TestGetPkgs(t *testing.T) { func TestGetPkgs(t *testing.T) {
ctx := context.Background() _, err := db.Open(":memory:")
database := prepareDb() if err != nil {
defer database.Close() t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
x1 := testPkg x1 := testPkg
x1.Name = "x1" x1.Name = "x1"
x2 := testPkg x2 := testPkg
x2.Name = "x2" x2.Name = "x2"
err := database.InsertPackage(ctx, x1) err = db.InsertPackage(x1)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
err = database.InsertPackage(ctx, x2) err = db.InsertPackage(x2)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
pkgs, err := database.GetPkgs(ctx, "name LIKE 'x%'") result, err := db.GetPkgs("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 _, dbPkg := range pkgs { for result.Next() {
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)
} }
@@ -148,26 +142,28 @@ func TestGetPkgs(t *testing.T) {
} }
func TestGetPkg(t *testing.T) { func TestGetPkg(t *testing.T) {
ctx := context.Background() _, err := db.Open(":memory:")
database := prepareDb() if err != nil {
defer database.Close() t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
x1 := testPkg x1 := testPkg
x1.Name = "x1" x1.Name = "x1"
x2 := testPkg x2 := testPkg
x2.Name = "x2" x2.Name = "x2"
err := database.InsertPackage(ctx, x1) err = db.InsertPackage(x1)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
err = database.InsertPackage(ctx, x2) err = db.InsertPackage(x2)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
pkg, err := database.GetPkg("name LIKE 'x%'") pkg, err := db.GetPkg("name LIKE 'x%' ORDER BY name")
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
@@ -182,70 +178,73 @@ func TestGetPkg(t *testing.T) {
} }
func TestDeletePkgs(t *testing.T) { func TestDeletePkgs(t *testing.T) {
ctx := context.Background() _, err := db.Open(":memory:")
database := prepareDb() if err != nil {
defer database.Close() t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
x1 := testPkg x1 := testPkg
x1.Name = "x1" x1.Name = "x1"
x2 := testPkg x2 := testPkg
x2.Name = "x2" x2.Name = "x2"
err := database.InsertPackage(ctx, x1) err = db.InsertPackage(x1)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
err = database.InsertPackage(ctx, x2) err = db.InsertPackage(x2)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
err = database.DeletePkgs(ctx, "name = 'x1'") err = db.DeletePkgs("name = 'x1'")
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 = db.DB().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) {
ctx := context.Background() _, err := db.Open(":memory:")
database := prepareDb() if err != nil {
defer database.Close() t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
x1 := testPkg x1 := testPkg
x1.Name = "x1" x1.Name = "x1"
x2 := testPkg x2 := testPkg
x2.Name = "x2" x2.Name = "x2"
x2.Provides = append(x2.Provides, "x") x2.Provides.Val = append(x2.Provides.Val, "x")
err := database.InsertPackage(ctx, x1) err = db.InsertPackage(x1)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
err = database.InsertPackage(ctx, x2) err = db.InsertPackage(x2)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
pkgs, err := database.GetPkgs(ctx, "name = 'x2'") var dbPkg db.Package
err = db.DB().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 len(pkgs) != 1 || pkgs[0].Name != "x2" { if dbPkg.Name != "x2" {
t.Errorf("Expected x2 package, got %v", pkgs) t.Errorf("Expected x2 package, got %s", dbPkg.Name)
}
// 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,52 +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/driver"
"encoding/json"
"errors"
"golang.org/x/exp/slices"
"modernc.org/sqlite"
)
func init() {
sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains)
}
// jsonArrayContains is an SQLite function that checks if a JSON array
// in the database contains a given value
func jsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
value, ok := args[0].(string)
if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
}
item, ok := args[1].(string)
if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
}
var array []string
err := json.Unmarshal([]byte(value), &array)
if err != nil {
return nil, err
}
return slices.Contains(array, item), nil
}

View File

@@ -1,21 +1,20 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// You should have received a copy of the GNU General Public License */
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Пакет dl содержит абстракции для загрузки файлов и каталогов // Пакет dl содержит абстракции для загрузки файлов и каталогов
// из различных источников. // из различных источников.
@@ -31,17 +30,18 @@ import (
"fmt" "fmt"
"hash" "hash"
"io" "io"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/PuerkitoBio/purell" "github.com/PuerkitoBio/purell"
"github.com/leonelquinteros/gotext"
"github.com/vmihailenco/msgpack/v5" "github.com/vmihailenco/msgpack/v5"
"golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2b"
"golang.org/x/crypto/blake2s" "golang.org/x/crypto/blake2s"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"plemya-x.ru/alr/internal/dlcache"
"plemya-x.ru/alr/pkg/loggerctx"
) )
// Константа для имени файла манифеста кэша // Константа для имени файла манифеста кэша
@@ -55,7 +55,7 @@ var (
// Массив доступных загрузчиков в порядке их проверки // Массив доступных загрузчиков в порядке их проверки
var Downloaders = []Downloader{ var Downloaders = []Downloader{
&GitDownloader{}, GitDownloader{},
TorrentDownloader{}, TorrentDownloader{},
FileDownloader{}, FileDownloader{},
} }
@@ -80,11 +80,6 @@ func (t Type) String() string {
return "<unknown>" return "<unknown>"
} }
type DlCache interface {
Get(context.Context, string) (string, bool)
New(context.Context, string) (string, error)
}
// Структура Options содержит параметры для загрузки файлов и каталогов // Структура Options содержит параметры для загрузки файлов и каталогов
type Options struct { type Options struct {
Hash []byte Hash []byte
@@ -96,7 +91,6 @@ type Options struct {
PostprocDisabled bool PostprocDisabled bool
Progress io.Writer Progress io.Writer
LocalDir string LocalDir string
DlCache DlCache
} }
// Метод для создания нового хеша на основе указанного алгоритма хеширования // Метод для создания нового хеша на основе указанного алгоритма хеширования
@@ -137,7 +131,7 @@ type Manifest struct {
type Downloader interface { type Downloader interface {
Name() string Name() string
MatchURL(string) bool MatchURL(string) bool
Download(context.Context, Options) (Type, string, error) Download(Options) (Type, string, error)
} }
// Интерфейс UpdatingDownloader расширяет Downloader методом Update // Интерфейс UpdatingDownloader расширяет Downloader методом Update
@@ -148,6 +142,7 @@ type UpdatingDownloader interface {
// Функция Download загружает файл или каталог с использованием указанных параметров // Функция Download загружает файл или каталог с использованием указанных параметров
func Download(ctx context.Context, opts Options) (err error) { func Download(ctx context.Context, opts Options) (err error) {
log := loggerctx.From(ctx)
normalized, err := normalizeURL(opts.URL) normalized, err := normalizeURL(opts.URL)
if err != nil { if err != nil {
return err return err
@@ -157,25 +152,26 @@ func Download(ctx context.Context, opts Options) (err error) {
d := getDownloader(opts.URL) d := getDownloader(opts.URL)
if opts.CacheDisabled { if opts.CacheDisabled {
_, _, err = d.Download(ctx, opts) _, _, err = d.Download(opts)
return err return err
} }
var t Type var t Type
cacheDir, ok := opts.DlCache.Get(ctx, opts.URL) cacheDir, ok := dlcache.Get(ctx, opts.URL)
if ok { if ok {
var updated bool var updated bool
if d, ok := d.(UpdatingDownloader); ok { if d, ok := d.(UpdatingDownloader); ok {
slog.Info( log.Info("Source can be updated, updating if required").Str("source", opts.Name).Str("downloader", d.Name()).Send()
gotext.Get("Source can be updated, updating if required"),
"source", opts.Name,
"downloader", d.Name(),
)
newOpts := opts updated, err = d.Update(Options{
newOpts.Destination = cacheDir Hash: opts.Hash,
HashAlgorithm: opts.HashAlgorithm,
updated, err = d.Update(newOpts) Name: opts.Name,
URL: opts.URL,
Destination: cacheDir,
Progress: opts.Progress,
LocalDir: opts.LocalDir,
})
if err != nil { if err != nil {
return err return err
} }
@@ -192,18 +188,10 @@ func Download(ctx context.Context, opts Options) (err error) {
} }
if ok && !updated { if ok && !updated {
slog.Info( log.Info("Source found in cache and linked to destination").Str("source", opts.Name).Stringer("type", t).Send()
gotext.Get("Source found in cache and linked to destination"),
"source", opts.Name,
"type", t,
)
return nil return nil
} else if ok { } else if ok {
slog.Info( log.Info("Source updated and linked to destination").Str("source", opts.Name).Stringer("type", t).Send()
gotext.Get("Source updated and linked to destination"),
"source", opts.Name,
"type", t,
)
return nil return nil
} }
} else { } else {
@@ -214,17 +202,22 @@ func Download(ctx context.Context, opts Options) (err error) {
} }
} }
slog.Info(gotext.Get("Downloading source"), "source", opts.Name, "downloader", d.Name()) log.Info("Downloading source").Str("source", opts.Name).Str("downloader", d.Name()).Send()
cacheDir, err = opts.DlCache.New(ctx, opts.URL) cacheDir, err = dlcache.New(ctx, opts.URL)
if err != nil { if err != nil {
return err return err
} }
newOpts := opts t, name, err := d.Download(Options{
newOpts.Destination = cacheDir Hash: opts.Hash,
HashAlgorithm: opts.HashAlgorithm,
t, name, err := d.Download(ctx, newOpts) Name: opts.Name,
URL: opts.URL,
Destination: cacheDir,
Progress: opts.Progress,
LocalDir: opts.LocalDir,
})
if err != nil { if err != nil {
return err return err
} }
@@ -280,14 +273,14 @@ func handleCache(cacheDir, dest, name string, t Type) (bool, error) {
cd.Close() cd.Close()
if slices.Contains(names, name) { if slices.Contains(names, name) {
err = linkOrCopy(filepath.Join(cacheDir, name), dest) err = os.Link(filepath.Join(cacheDir, name), dest)
if err != nil { if err != nil {
return false, err return false, err
} }
return true, nil return true, nil
} }
case TypeDir: case TypeDir:
err := linkOrCopyDir(cacheDir, dest) err := linkDir(cacheDir, dest)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -296,40 +289,8 @@ func handleCache(cacheDir, dest, name string, t Type) (bool, error) {
return false, nil return false, nil
} }
// linkOrCopy пытается создать жесткую ссылку, а если не получается - копирует файл // Функция linkDir рекурсивно создает жесткие ссылки для файлов из каталога src в каталог dest
func linkOrCopy(src, dest string) error { func linkDir(src, dest string) error {
err := os.Link(src, dest)
if err != nil {
// Если не удалось создать ссылку, копируем файл
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := os.Create(dest)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
if err != nil {
return err
}
// Копируем права доступа
srcInfo, err := srcFile.Stat()
if err != nil {
return err
}
return os.Chmod(dest, srcInfo.Mode())
}
return nil
}
// linkOrCopyDir рекурсивно создает жесткие ссылки или копирует файлы из каталога src в каталог dest
func linkOrCopyDir(src, dest string) error {
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
@@ -349,7 +310,7 @@ func linkOrCopyDir(src, dest string) error {
return os.MkdirAll(newPath, info.Mode()) return os.MkdirAll(newPath, info.Mode())
} }
return linkOrCopy(path, newPath) return os.Link(path, newPath)
}) })
} }

View File

@@ -1,28 +1,26 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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 dl package dl
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"io" "io"
"mime" "mime"
"net/http" "net/http"
@@ -31,8 +29,12 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/mholt/archiver/v4" "github.com/mholt/archiver/v4"
"github.com/schollz/progressbar/v3"
"plemya-x.ru/alr/internal/shutils/handlers"
) )
// FileDownloader загружает файлы с использованием HTTP // FileDownloader загружает файлы с использованием HTTP
@@ -51,7 +53,7 @@ func (FileDownloader) MatchURL(string) bool {
// Download загружает файл с использованием HTTP. Если файл // Download загружает файл с использованием HTTP. Если файл
// сжат в поддерживаемом формате, он будет распакован // сжат в поддерживаемом формате, он будет распакован
func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string, error) { func (FileDownloader) Download(opts Options) (Type, string, error) {
// Разбор URL // Разбор URL
u, err := url.Parse(opts.URL) u, err := url.Parse(opts.URL)
if err != nil { if err != nil {
@@ -91,12 +93,8 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string,
} }
r = localFl r = localFl
} else { } else {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return 0, "", fmt.Errorf("failed to create request: %w", err)
}
// Выполнение HTTP GET запроса // Выполнение HTTP GET запроса
res, err := http.DefaultClient.Do(req) res, err := http.Get(u.String())
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
@@ -108,22 +106,37 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string,
} }
defer r.Close() defer r.Close()
postprocDisabled := opts.PostprocDisabled || archive == "false" opts.PostprocDisabled = archive == "false"
path := filepath.Join(opts.Destination, name) path := filepath.Join(opts.Destination, name)
fl, err := os.Create(path) fl, err := os.Create(path)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
defer fl.Close()
var out io.WriteCloser var bar io.WriteCloser
// Настройка индикатора прогресса // Настройка индикатора прогресса
if opts.Progress != nil { if opts.Progress != nil {
out = NewProgressWriter(fl, size, name, opts.Progress) bar = progressbar.NewOptions64(
size,
progressbar.OptionSetDescription(name),
progressbar.OptionSetWriter(opts.Progress),
progressbar.OptionShowBytes(true),
progressbar.OptionSetWidth(10),
progressbar.OptionThrottle(65*time.Millisecond),
progressbar.OptionShowCount(),
progressbar.OptionOnCompletion(func() {
_, _ = io.WriteString(opts.Progress, "\n")
}),
progressbar.OptionSpinnerType(14),
progressbar.OptionFullWidth(),
progressbar.OptionSetRenderBlankState(true),
)
defer bar.Close()
} else { } else {
out = fl bar = handlers.NopRWC{}
} }
defer out.Close()
h, err := opts.NewHash() h, err := opts.NewHash()
if err != nil { if err != nil {
@@ -133,9 +146,9 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string,
var w io.Writer var w io.Writer
// Настройка MultiWriter для записи в файл, хеш и индикатор прогресса // Настройка MultiWriter для записи в файл, хеш и индикатор прогресса
if opts.Hash != nil { if opts.Hash != nil {
w = io.MultiWriter(h, out) w = io.MultiWriter(fl, h, bar)
} else { } else {
w = io.MultiWriter(out) w = io.MultiWriter(fl, bar)
} }
// Копирование содержимого из источника в файл назначения // Копирование содержимого из источника в файл назначения
@@ -154,7 +167,7 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string,
} }
// Проверка необходимости постобработки // Проверка необходимости постобработки
if postprocDisabled { if opts.PostprocDisabled {
return TypeFile, name, nil return TypeFile, name, nil
} }

View File

@@ -1,28 +1,25 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. /*
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. * ALR - Any Linux Repository
// * Copyright (C) 2024 Евгений Храмов
// 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
// This program is free software: you can redistribute it and/or modify * the Free Software Foundation, either version 3 of the License, or
// it under the terms of the GNU General Public License as published by * (at your option) any later version.
// 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
// This program is distributed in the hope that it will be useful, * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// but WITHOUT ANY WARRANTY; without even the implied warranty of * GNU General Public License for more details.
// 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/>.
// 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 dl package dl
import ( import (
"context"
"errors" "errors"
"fmt"
"net/url" "net/url"
"path" "path"
"strconv" "strconv"
@@ -49,7 +46,7 @@ func (GitDownloader) MatchURL(u string) bool {
// Download uses git to clone the repository from the specified URL. // Download uses git to clone the repository from the specified URL.
// It allows specifying the revision, depth and recursion options // It allows specifying the revision, depth and recursion options
// via query string // via query string
func (d *GitDownloader) Download(ctx context.Context, opts Options) (Type, string, error) { func (GitDownloader) Download(opts Options) (Type, string, error) {
u, err := url.Parse(opts.URL) u, err := url.Parse(opts.URL)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
@@ -61,9 +58,6 @@ func (d *GitDownloader) Download(ctx context.Context, opts Options) (Type, strin
rev := query.Get("~rev") rev := query.Get("~rev")
query.Del("~rev") query.Del("~rev")
// Right now, this only affects the return value of name,
// which will be used by dl_cache.
// It seems wrong, but for now it's better to leave it as it is.
name := query.Get("~name") name := query.Get("~name")
query.Del("~name") query.Del("~name")
@@ -94,7 +88,7 @@ func (d *GitDownloader) Download(ctx context.Context, opts Options) (Type, strin
co.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth co.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
} }
r, err := git.PlainCloneContext(ctx, opts.Destination, false, co) r, err := git.PlainClone(opts.Destination, false, co)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
@@ -125,11 +119,6 @@ func (d *GitDownloader) Download(ctx context.Context, opts Options) (Type, strin
} }
} }
err = VerifyHashFromLocal("", opts)
if err != nil {
return 0, "", err
}
if name == "" { if name == "" {
name = strings.TrimSuffix(path.Base(u.Path), ".git") name = strings.TrimSuffix(path.Base(u.Path), ".git")
} }
@@ -142,7 +131,7 @@ func (d *GitDownloader) Download(ctx context.Context, opts Options) (Type, strin
// and recursion options via query string. It returns // and recursion options via query string. It returns
// true if update was successful and false if the // true if update was successful and false if the
// repository is already up-to-date // repository is already up-to-date
func (d *GitDownloader) Update(opts Options) (bool, error) { func (GitDownloader) Update(opts Options) (bool, error) {
u, err := url.Parse(opts.URL) u, err := url.Parse(opts.URL)
if err != nil { if err != nil {
return false, err return false, err
@@ -150,7 +139,6 @@ func (d *GitDownloader) Update(opts Options) (bool, error) {
u.Scheme = strings.TrimPrefix(u.Scheme, "git+") u.Scheme = strings.TrimPrefix(u.Scheme, "git+")
query := u.Query() query := u.Query()
rev := query.Get("~rev")
query.Del("~rev") query.Del("~rev")
depthStr := query.Get("~depth") depthStr := query.Get("~depth")
@@ -179,75 +167,32 @@ func (d *GitDownloader) Update(opts Options) (bool, error) {
} }
} }
// First, we do a fetch to get all the revisions. po := &git.PullOptions{
fo := &git.FetchOptions{ Depth: depth,
Depth: depth, Progress: opts.Progress,
Progress: opts.Progress, RecurseSubmodules: git.NoRecurseSubmodules,
}
if recursive == "true" {
po.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
} }
m, err := getManifest(opts.Destination) m, err := getManifest(opts.Destination)
manifestOK := err == nil manifestOK := err == nil
err = r.Fetch(fo) err = w.Pull(po)
if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { if errors.Is(err, git.NoErrAlreadyUpToDate) {
return false, err return false, nil
} } else if err != nil {
// If a revision is specified, switch to it.
if rev != "" {
// We are trying to find the revision as a hash of the commit
hash, err := r.ResolveRevision(plumbing.Revision(rev))
if err != nil {
return false, fmt.Errorf("failed to resolve revision %s: %w", rev, err)
}
err = w.Checkout(&git.CheckoutOptions{
Hash: *hash,
})
if err != nil {
return false, fmt.Errorf("failed to checkout revision %s: %w", rev, err)
}
if recursive == "true" {
submodules, err := w.Submodules()
if err == nil {
err = submodules.Update(&git.SubmoduleUpdateOptions{
Init: true,
})
if err != nil {
return false, fmt.Errorf("failed to update submodules %s: %w", rev, err)
}
}
}
} else {
// If the revision is not specified, we do a regular pull.
po := &git.PullOptions{
Depth: depth,
Progress: opts.Progress,
RecurseSubmodules: git.NoRecurseSubmodules,
}
if recursive == "true" {
po.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
}
err = w.Pull(po)
if err != nil {
if errors.Is(err, git.NoErrAlreadyUpToDate) {
return false, nil
}
return false, err
}
}
err = VerifyHashFromLocal("", opts)
if err != nil {
return false, err return false, err
} }
if manifestOK { if manifestOK {
err = writeManifest(opts.Destination, m) err = writeManifest(opts.Destination, m)
if err != nil {
return true, err
}
} }
return true, err return true, nil
} }

View File

@@ -1,26 +1,6 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// 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 dl package dl
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@@ -50,7 +30,7 @@ func (TorrentDownloader) MatchURL(u string) bool {
} }
// Download downloads a file over the BitTorrent protocol. // Download downloads a file over the BitTorrent protocol.
func (TorrentDownloader) Download(ctx context.Context, opts Options) (Type, string, error) { func (TorrentDownloader) Download(opts Options) (Type, string, error) {
aria2Path, err := exec.LookPath("aria2c") aria2Path, err := exec.LookPath("aria2c")
if err != nil { if err != nil {
return 0, "", ErrAria2NotFound return 0, "", ErrAria2NotFound
@@ -58,7 +38,7 @@ func (TorrentDownloader) Download(ctx context.Context, opts Options) (Type, stri
opts.URL = strings.TrimPrefix(opts.URL, "torrent+") opts.URL = strings.TrimPrefix(opts.URL, "torrent+")
cmd := exec.CommandContext(ctx, aria2Path, "--summary-interval=0", "--log-level=warn", "--seed-time=0", "--dir="+opts.Destination, opts.URL) cmd := exec.Command(aria2Path, "--summary-interval=0", "--log-level=warn", "--seed-time=0", "--dir="+opts.Destination, opts.URL)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err = cmd.Run() err = cmd.Run()
@@ -71,17 +51,7 @@ func (TorrentDownloader) Download(ctx context.Context, opts Options) (Type, stri
return 0, "", err return 0, "", err
} }
dlType, name, err := determineType(opts.Destination) return determineType(opts.Destination)
if err != nil {
return 0, "", err
}
err = VerifyHashFromLocal(name, opts)
if err != nil {
return 0, "", err
}
return dlType, name, nil
} }
func removeTorrentFiles(path string) error { func removeTorrentFiles(path string) error {

View File

@@ -0,0 +1,93 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* 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 dlcache
import (
"context"
"crypto/sha1"
"encoding/hex"
"io"
"os"
"path/filepath"
"plemya-x.ru/alr/internal/config"
)
// BasePath returns the base path of the download cache
func BasePath(ctx context.Context) string {
return filepath.Join(config.GetPaths(ctx).CacheDir, "dl")
}
// New creates a new directory with the given ID in the cache.
// If a directory with the same ID already exists,
// it will be deleted before creating a new one.
func New(ctx context.Context, id string) (string, error) {
h, err := hashID(id)
if err != nil {
return "", err
}
itemPath := filepath.Join(BasePath(ctx), h)
fi, err := os.Stat(itemPath)
if err == nil || (fi != nil && !fi.IsDir()) {
err = os.RemoveAll(itemPath)
if err != nil {
return "", err
}
}
err = os.MkdirAll(itemPath, 0o755)
if err != nil {
return "", err
}
return itemPath, nil
}
// Get checks if an entry with the given ID
// already exists in the cache, and if so,
// returns the directory and true. If it
// does not exist, it returns an empty string
// and false.
func Get(ctx context.Context, id string) (string, bool) {
h, err := hashID(id)
if err != nil {
return "", false
}
itemPath := filepath.Join(BasePath(ctx), h)
_, err = os.Stat(itemPath)
if err != nil {
return "", false
}
return itemPath, true
}
// hashID hashes the input ID with SHA1
// and returns the hex string of the hashed
// ID.
func hashID(id string) (string, error) {
h := sha1.New()
_, err := io.WriteString(h, id)
if err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}

View File

@@ -0,0 +1,76 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* 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 dlcache_test
import (
"context"
"crypto/sha1"
"encoding/hex"
"io"
"os"
"path/filepath"
"testing"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/dlcache"
)
func init() {
dir, err := os.MkdirTemp("/tmp", "alr-dlcache-test.*")
if err != nil {
panic(err)
}
config.GetPaths(context.Background()).RepoDir = dir
}
func TestNew(t *testing.T) {
const id = "https://example.com"
dir, err := dlcache.New(id)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
exp := filepath.Join(dlcache.BasePath(), sha1sum(id))
if dir != exp {
t.Errorf("Expected %s, got %s", exp, dir)
}
fi, err := os.Stat(dir)
if err != nil {
t.Errorf("stat: expected no error, got %s", err)
}
if !fi.IsDir() {
t.Errorf("Expected cache item to be a directory")
}
dir2, ok := dlcache.Get(id)
if !ok {
t.Errorf("Expected Get() to return valid value")
}
if dir2 != dir {
t.Errorf("Expected %s from Get(), got %s", dir, dir2)
}
}
func sha1sum(id string) string {
h := sha1.New()
_, _ = io.WriteString(h, id)
return hex.EncodeToString(h.Sum(nil))
}

View File

@@ -1,663 +0,0 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// 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 gen
import (
_ "embed"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"text/template"
)
// Встраиваем шаблон для AUR пакетов
//
//go:embed tmpls/aur.tmpl.sh
var aurTmpl string
// AUROptions содержит параметры для генерации шаблона AUR
type AUROptions struct {
Name string // Имя пакета в AUR
Version string // Версия пакета (опционально, если не указана - берется последняя)
CreateDir bool // Создавать ли директорию для пакета и дополнительные файлы
}
// aurAPIResponse представляет структуру ответа от API AUR
type aurAPIResponse struct {
Version int `json:"version"` // Версия API
Type string `json:"type"` // Тип ответа
ResultCount int `json:"resultcount"` // Количество результатов
Results []aurResult `json:"results"` // Массив результатов
Error string `json:"error"` // Сообщение об ошибке (если есть)
}
// aurResult содержит информацию о пакете из AUR
type aurResult struct {
ID int `json:"ID"`
Name string `json:"Name"`
PackageBaseID int `json:"PackageBaseID"`
PackageBase string `json:"PackageBase"`
Version string `json:"Version"`
Description string `json:"Description"`
URL string `json:"URL"`
NumVotes int `json:"NumVotes"`
Popularity float64 `json:"Popularity"`
OutOfDate *int `json:"OutOfDate"`
Maintainer string `json:"Maintainer"`
FirstSubmitted int `json:"FirstSubmitted"`
LastModified int `json:"LastModified"`
URLPath string `json:"URLPath"`
License []string `json:"License"`
Keywords []string `json:"Keywords"`
Depends []string `json:"Depends"`
MakeDepends []string `json:"MakeDepends"`
OptDepends []string `json:"OptDepends"`
CheckDepends []string `json:"CheckDepends"`
Conflicts []string `json:"Conflicts"`
Provides []string `json:"Provides"`
Replaces []string `json:"Replaces"`
// Дополнительные поля для данных из PKGBUILD
Sources []string `json:"-"`
Checksums []string `json:"-"`
BuildFunc string `json:"-"`
PackageFunc string `json:"-"`
PrepareFunc string `json:"-"`
PackageType string `json:"-"` // python, go, rust, cpp, nodejs, bin, git
HasDesktop bool `json:"-"` // Есть ли desktop файлы
HasSystemd bool `json:"-"` // Есть ли systemd сервисы
HasVersion bool `json:"-"` // Есть ли функция version()
HasScripts []string `json:"-"` // Дополнительные скрипты (postinstall, postremove, etc)
HasPatches bool `json:"-"` // Есть ли патчи
Architectures []string `json:"-"` // Поддерживаемые архитектуры
// Автоматически определяемые файлы для install-* команд
BinaryFiles []string `json:"-"` // Исполняемые файлы для install-binary
LicenseFiles []string `json:"-"` // Лицензионные файлы для install-license
ManualFiles []string `json:"-"` // Man страницы для install-manual
DesktopFiles []string `json:"-"` // Desktop файлы для install-desktop
ServiceFiles []string `json:"-"` // Systemd сервисы для install-systemd
CompletionFiles map[string]string `json:"-"` // Файлы автодополнения по типу (bash, zsh, fish)
}
// Вспомогательные методы для шаблона
func (r aurResult) LicenseString() string {
if len(r.License) == 0 {
return "custom:Unknown"
}
// Форматируем лицензии для alr.sh
licenses := make([]string, len(r.License))
for i, l := range r.License {
licenses[i] = fmt.Sprintf("'%s'", l)
}
return strings.Join(licenses, " ")
}
func (r aurResult) DependsString() string {
if len(r.Depends) == 0 {
return ""
}
deps := make([]string, len(r.Depends))
for i, d := range r.Depends {
// Убираем версионные ограничения для простоты
dep := strings.Split(d, ">=")[0]
dep = strings.Split(dep, "<=")[0]
dep = strings.Split(dep, "=")[0]
dep = strings.Split(dep, ">")[0]
dep = strings.Split(dep, "<")[0]
deps[i] = fmt.Sprintf("'%s'", dep)
}
return strings.Join(deps, " ")
}
func (r aurResult) MakeDependsString() string {
if len(r.MakeDepends) == 0 {
return ""
}
deps := make([]string, len(r.MakeDepends))
for i, d := range r.MakeDepends {
// Убираем версионные ограничения для простоты
dep := strings.Split(d, ">=")[0]
dep = strings.Split(dep, "<=")[0]
dep = strings.Split(dep, "=")[0]
dep = strings.Split(dep, ">")[0]
dep = strings.Split(dep, "<")[0]
deps[i] = fmt.Sprintf("'%s'", dep)
}
return strings.Join(deps, " ")
}
func (r aurResult) GitURL() string {
// Формируем URL для клонирования из AUR
return fmt.Sprintf("https://aur.archlinux.org/%s.git", r.PackageBase)
}
func (r aurResult) ArchitecturesString() string {
if len(r.Architectures) == 0 {
return "'all'"
}
archs := make([]string, len(r.Architectures))
for i, arch := range r.Architectures {
archs[i] = fmt.Sprintf("'%s'", arch)
}
return strings.Join(archs, " ")
}
func (r aurResult) OptDependsString() string {
if len(r.OptDepends) == 0 {
return ""
}
optDeps := make([]string, 0, len(r.OptDepends))
for _, dep := range r.OptDepends {
// Форматируем опциональные зависимости для alr.sh
parts := strings.SplitN(dep, ": ", 2)
if len(parts) == 2 {
optDeps = append(optDeps, fmt.Sprintf("'%s: %s'", parts[0], parts[1]))
} else {
optDeps = append(optDeps, fmt.Sprintf("'%s'", dep))
}
}
return strings.Join(optDeps, "\n\t")
}
func (r aurResult) ScriptsString() string {
if len(r.HasScripts) == 0 {
return ""
}
scripts := make([]string, len(r.HasScripts))
for i, script := range r.HasScripts {
scripts[i] = fmt.Sprintf("['%s']='%s.sh'", script, script)
}
return strings.Join(scripts, "\n\t")
}
// GenerateInstallCommands генерирует команды install-* для шаблона
func (r aurResult) GenerateInstallCommands() string {
var commands []string
// install-binary команды
for _, binary := range r.BinaryFiles {
if binary == "./"+r.Name {
commands = append(commands, fmt.Sprintf("\tinstall-binary %s", binary))
} else {
commands = append(commands, fmt.Sprintf("\tinstall-binary %s %s", binary, r.Name))
}
}
// install-license команды
for _, license := range r.LicenseFiles {
commands = append(commands, fmt.Sprintf("\tinstall-license %s %s/LICENSE", license, r.Name))
}
// install-manual команды
for _, manual := range r.ManualFiles {
commands = append(commands, fmt.Sprintf("\tinstall-manual %s", manual))
}
// install-desktop команды
for _, desktop := range r.DesktopFiles {
commands = append(commands, fmt.Sprintf("\tinstall-desktop %s", desktop))
}
// install-systemd команды
for _, service := range r.ServiceFiles {
if strings.Contains(service, "user") {
commands = append(commands, fmt.Sprintf("\tinstall-systemd-user %s", service))
} else {
commands = append(commands, fmt.Sprintf("\tinstall-systemd %s", service))
}
}
// install-completion команды
for shell, file := range r.CompletionFiles {
switch shell {
case "bash":
commands = append(commands, fmt.Sprintf("\tinstall-completion bash %s < %s", r.Name, file))
case "zsh":
commands = append(commands, fmt.Sprintf("\tinstall-completion zsh %s < %s", r.Name, file))
case "fish":
commands = append(commands, fmt.Sprintf("\t%s completion fish | install-completion fish %s", r.Name, r.Name))
}
}
if len(commands) == 0 {
return "\t# TODO: Добавьте команды установки файлов"
}
return strings.Join(commands, "\n")
}
// fetchPKGBUILD загружает PKGBUILD файл для пакета
func fetchPKGBUILD(packageBase string) (string, error) {
// URL для raw PKGBUILD
pkgbuildURL := fmt.Sprintf("https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=%s", packageBase)
res, err := http.Get(pkgbuildURL)
if err != nil {
return "", fmt.Errorf("failed to fetch PKGBUILD: %w", err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
return "", fmt.Errorf("failed to fetch PKGBUILD: status %s", res.Status)
}
data, err := io.ReadAll(res.Body)
if err != nil {
return "", fmt.Errorf("failed to read PKGBUILD: %w", err)
}
return string(data), nil
}
// parseSources извлекает источники из PKGBUILD
func parseSources(pkgbuild string) []string {
var sources []string
// Регулярное выражение для поиска массива source
// Поддерживает как однострочные, так и многострочные определения
sourceRegex := regexp.MustCompile(`(?ms)source=\((.*?)\)`)
matches := sourceRegex.FindStringSubmatch(pkgbuild)
if len(matches) > 1 {
// Извлекаем содержимое массива source
sourceContent := matches[1]
// Разбираем элементы массива
// Учитываем кавычки и переносы строк
elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`)
elements := elemRegex.FindAllStringSubmatch(sourceContent, -1)
for _, elem := range elements {
if len(elem) > 1 {
source := elem[1]
// Заменяем переменные версии
source = strings.ReplaceAll(source, "$pkgver", "${version}")
source = strings.ReplaceAll(source, "${pkgver}", "${version}")
source = strings.ReplaceAll(source, "$pkgname", "${name}")
source = strings.ReplaceAll(source, "${pkgname}", "${name}")
// Обрабатываем другие переменные (упрощенно)
source = strings.ReplaceAll(source, "$_commit", "${_commit}")
sources = append(sources, source)
}
}
}
// Если источники не найдены в source=(), проверяем source_x86_64 и другие архитектуры
if len(sources) == 0 {
archSourceRegex := regexp.MustCompile(`(?ms)source_(?:x86_64|aarch64)=\((.*?)\)`)
matches = archSourceRegex.FindStringSubmatch(pkgbuild)
if len(matches) > 1 {
sourceContent := matches[1]
elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`)
elements := elemRegex.FindAllStringSubmatch(sourceContent, -1)
for _, elem := range elements {
if len(elem) > 1 {
source := elem[1]
source = strings.ReplaceAll(source, "$pkgver", "${version}")
source = strings.ReplaceAll(source, "${pkgver}", "${version}")
source = strings.ReplaceAll(source, "$pkgname", "${name}")
source = strings.ReplaceAll(source, "${pkgname}", "${name}")
sources = append(sources, source)
}
}
}
}
return sources
}
// parseChecksums извлекает контрольные суммы из PKGBUILD
func parseChecksums(pkgbuild string) []string {
var checksums []string
// Пробуем разные типы контрольных сумм
for _, hashType := range []string{"sha256sums", "sha512sums", "sha1sums", "md5sums", "b2sums"} {
regex := regexp.MustCompile(fmt.Sprintf(`(?ms)%s=\((.*?)\)`, hashType))
matches := regex.FindStringSubmatch(pkgbuild)
if len(matches) > 1 {
content := matches[1]
elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`)
elements := elemRegex.FindAllStringSubmatch(content, -1)
for _, elem := range elements {
if len(elem) > 1 {
checksums = append(checksums, elem[1])
}
}
if len(checksums) > 0 {
break // Используем первый найденный тип хешей
}
}
}
return checksums
}
// parseFunctions извлекает функции build(), package() и prepare() из PKGBUILD
func parseFunctions(pkgbuild string) (buildFunc, packageFunc, prepareFunc string) {
// Извлекаем функцию build()
buildRegex := regexp.MustCompile(`(?ms)^build\(\)\s*\{(.*?)^\}`)
if matches := buildRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 {
buildFunc = strings.TrimSpace(matches[1])
}
// Извлекаем функцию package()
packageRegex := regexp.MustCompile(`(?ms)^package\(\)\s*\{(.*?)^\}`)
if matches := packageRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 {
packageFunc = strings.TrimSpace(matches[1])
}
// Извлекаем функцию prepare()
prepareRegex := regexp.MustCompile(`(?ms)^prepare\(\)\s*\{(.*?)^\}`)
if matches := prepareRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 {
prepareFunc = strings.TrimSpace(matches[1])
}
return buildFunc, packageFunc, prepareFunc
}
// detectInstallableFiles анализирует PKGBUILD и определяет файлы для install-* команд
func detectInstallableFiles(pkg *aurResult, pkgbuild string) {
// Инициализируем карту для файлов автодополнения
pkg.CompletionFiles = make(map[string]string)
// Для простоты, добавляем стандартные файлы для типа пакета
switch pkg.PackageType {
case "go":
pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name)
case "rust":
pkg.BinaryFiles = append(pkg.BinaryFiles, "./target/release/"+pkg.Name)
case "cpp", "meson":
pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name) // обычно в корне после сборки
case "bin":
pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name)
default:
if pkg.PackageType != "python" && pkg.PackageType != "nodejs" {
pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name)
}
}
// Ищем лицензионные файлы для install-license с более точными паттернами
licenseRegex := regexp.MustCompile(`(?i)\b(LICENSE|COPYING|COPYRIGHT|LICENCE)(?:\.[a-zA-Z0-9]+)?\b`)
licenseMatches := licenseRegex.FindAllString(pkgbuild, -1)
for _, match := range licenseMatches {
// Фильтруем только реальные файлы лицензий
if strings.Contains(strings.ToLower(match), "license") ||
strings.Contains(strings.ToLower(match), "copying") ||
strings.Contains(strings.ToLower(match), "copyright") {
if !contains(pkg.LicenseFiles, "./"+match) {
pkg.LicenseFiles = append(pkg.LicenseFiles, "./"+match)
}
}
}
// Если не найдены лицензионные файлы, добавляем стандартные
if len(pkg.LicenseFiles) == 0 {
pkg.LicenseFiles = append(pkg.LicenseFiles, "LICENSE")
}
// Ищем man страницы для install-manual с более точными паттернами
manRegex := regexp.MustCompile(`\b\w+\.(?:1|2|3|4|5|6|7|8)(?:\.gz)?\b`)
manMatches := manRegex.FindAllString(pkgbuild, -1)
for _, match := range manMatches {
// Проверяем, что это не переменная или часть кода
if !strings.Contains(match, "$") && !strings.Contains(match, "{") {
if !contains(pkg.ManualFiles, "./"+match) {
pkg.ManualFiles = append(pkg.ManualFiles, "./"+match)
}
}
}
// Ищем desktop файлы для install-desktop
desktopRegex := regexp.MustCompile(`[^/\s]*\.desktop`)
desktopMatches := desktopRegex.FindAllString(pkgbuild, -1)
for _, match := range desktopMatches {
if !contains(pkg.DesktopFiles, "./"+match) {
pkg.DesktopFiles = append(pkg.DesktopFiles, "./"+match)
}
}
// Ищем systemd сервисы для install-systemd
serviceRegex := regexp.MustCompile(`[^/\s]*\.service`)
serviceMatches := serviceRegex.FindAllString(pkgbuild, -1)
for _, match := range serviceMatches {
if !contains(pkg.ServiceFiles, "./"+match) {
pkg.ServiceFiles = append(pkg.ServiceFiles, "./"+match)
}
}
// Ищем файлы автодополнения
completionPatterns := map[string]string{
"bash": `completions?/.*\.bash|bash-completion`,
"zsh": `completions?/.*\.zsh|zsh.*completion`,
"fish": `completions?/.*\.fish|fish.*completion`,
}
for shell, pattern := range completionPatterns {
regex := regexp.MustCompile(fmt.Sprintf(`(?i)%s`, pattern))
matches := regex.FindAllString(pkgbuild, -1)
if len(matches) > 0 {
pkg.CompletionFiles[shell] = matches[0]
}
}
}
// contains проверяет, содержит ли слайс строк указанную строку
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
// detectPackageType определяет тип пакета на основе имени, зависимостей и источников
func detectPackageType(pkg *aurResult, pkgbuild string) {
name := strings.ToLower(pkg.Name)
// Определяем тип на основе имени пакета
switch {
case strings.HasPrefix(name, "python") || strings.HasPrefix(name, "python3-"):
pkg.PackageType = "python"
case strings.Contains(name, "nodejs") || strings.Contains(name, "node-"):
pkg.PackageType = "nodejs"
case strings.HasSuffix(name, "-bin"):
pkg.PackageType = "bin"
case strings.HasSuffix(name, "-git"):
pkg.PackageType = "git"
pkg.HasVersion = true // Git пакеты обычно имеют функцию version()
case strings.Contains(name, "rust") || hasRustSources(pkg.Sources):
pkg.PackageType = "rust"
case strings.Contains(name, "go-") || hasGoSources(pkg.Sources):
pkg.PackageType = "go"
case strings.Contains(name, "-rust") || strings.Contains(name, "paru") || strings.Contains(name, "cargo-"):
pkg.PackageType = "rust"
default:
// Определяем по зависимостям сборки
for _, dep := range pkg.MakeDepends {
depLower := strings.ToLower(dep)
switch {
case strings.Contains(depLower, "meson") || strings.Contains(depLower, "ninja"):
pkg.PackageType = "meson"
case strings.Contains(depLower, "cmake") || strings.Contains(depLower, "gcc") || strings.Contains(depLower, "clang"):
pkg.PackageType = "cpp"
case strings.Contains(depLower, "python"):
pkg.PackageType = "python"
case strings.Contains(depLower, "go"):
pkg.PackageType = "go"
case strings.Contains(depLower, "rust") || strings.Contains(depLower, "cargo"):
pkg.PackageType = "rust"
case strings.Contains(depLower, "npm") || strings.Contains(depLower, "nodejs"):
pkg.PackageType = "nodejs"
}
if pkg.PackageType != "" {
break
}
}
}
// Определяем архитектуры на основе типа пакета
if pkg.PackageType == "bin" {
pkg.Architectures = []string{"amd64"} // Бинарные пакеты обычно специфичны для архитектуры
} else {
pkg.Architectures = []string{"all"} // Исходный код собирается для любой архитектуры
}
// Определяем наличие desktop файлов
pkg.HasDesktop = strings.Contains(pkgbuild, ".desktop") ||
strings.Contains(pkgbuild, "install-desktop") ||
strings.Contains(pkgbuild, "xdg-desktop")
// Определяем наличие systemd сервисов
pkg.HasSystemd = strings.Contains(pkgbuild, ".service") ||
strings.Contains(pkgbuild, "systemctl") ||
strings.Contains(pkgbuild, "install-systemd")
// Определяем наличие функции version() для -git пакетов
pkg.HasVersion = strings.Contains(pkgbuild, "pkgver()") ||
(strings.HasSuffix(name, "-git") && strings.Contains(pkgbuild, "git describe"))
// Определяем наличие патчей
pkg.HasPatches = strings.Contains(pkgbuild, "patch ") ||
strings.Contains(pkgbuild, ".patch") ||
strings.Contains(pkgbuild, ".diff")
// Определяем дополнительные скрипты
if strings.Contains(pkgbuild, "post_install") {
pkg.HasScripts = append(pkg.HasScripts, "postinstall")
}
if strings.Contains(pkgbuild, "pre_remove") || strings.Contains(pkgbuild, "post_remove") {
pkg.HasScripts = append(pkg.HasScripts, "postremove")
}
}
// hasRustSources проверяет, содержат ли источники Rust проекты
func hasRustSources(sources []string) bool {
for _, src := range sources {
if strings.Contains(src, "crates.io") || strings.Contains(src, "Cargo.toml") {
return true
}
}
return false
}
// hasGoSources проверяет, содержат ли источники Go проекты
func hasGoSources(sources []string) bool {
for _, src := range sources {
if strings.Contains(src, "github.com") && strings.Contains(src, "/go") {
return true
}
}
return false
}
// AUR генерирует шаблон alr.sh на основе пакета из AUR
func AUR(w io.Writer, opts AUROptions) error {
// Создаем шаблон с функциями
tmpl, err := template.New("aur").
Funcs(funcs).
Parse(aurTmpl)
if err != nil {
return err
}
// Формируем URL запроса к AUR API
apiURL := "https://aur.archlinux.org/rpc/v5/info"
params := url.Values{}
params.Add("arg[]", opts.Name)
fullURL := fmt.Sprintf("%s?%s", apiURL, params.Encode())
// Выполняем запрос к AUR API
res, err := http.Get(fullURL)
if err != nil {
return fmt.Errorf("failed to fetch AUR package info: %w", err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
return fmt.Errorf("AUR API returned status: %s", res.Status)
}
// Декодируем ответ
var resp aurAPIResponse
err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil {
return fmt.Errorf("failed to decode AUR response: %w", err)
}
// Проверяем наличие ошибки в ответе
if resp.Error != "" {
return fmt.Errorf("AUR API error: %s", resp.Error)
}
// Проверяем, что пакет найден
if resp.ResultCount == 0 {
return fmt.Errorf("package '%s' not found in AUR", opts.Name)
}
// Берем первый результат
pkg := resp.Results[0]
// Если указана версия, проверяем соответствие
if opts.Version != "" && pkg.Version != opts.Version {
// Предупреждаем, но продолжаем с актуальной версией из AUR
fmt.Fprintf(w, "# WARNING: Requested version %s, but AUR has %s\n", opts.Version, pkg.Version)
}
// Загружаем PKGBUILD для получения источников
pkgbuild, err := fetchPKGBUILD(pkg.PackageBase)
if err != nil {
// Если не удалось загрузить PKGBUILD, используем fallback на AUR репозиторий
fmt.Fprintf(w, "# WARNING: Could not fetch PKGBUILD: %v\n", err)
fmt.Fprintf(w, "# Using AUR repository as source\n")
pkg.Sources = []string{fmt.Sprintf("%s::git+%s", pkg.Name, pkg.GitURL())}
pkg.Checksums = []string{"SKIP"}
} else {
// Извлекаем источники из PKGBUILD
pkg.Sources = parseSources(pkgbuild)
pkg.Checksums = parseChecksums(pkgbuild)
pkg.BuildFunc, pkg.PackageFunc, pkg.PrepareFunc = parseFunctions(pkgbuild)
// Определяем тип пакета
detectPackageType(&pkg, pkgbuild)
// Определяем файлы для install-* команд
detectInstallableFiles(&pkg, pkgbuild)
// Если источники не найдены, используем fallback
if len(pkg.Sources) == 0 {
fmt.Fprintf(w, "# WARNING: No sources found in PKGBUILD\n")
fmt.Fprintf(w, "# Using AUR repository as source\n")
pkg.Sources = []string{fmt.Sprintf("%s::git+%s", pkg.Name, pkg.GitURL())}
pkg.Checksums = []string{"SKIP"}
}
}
// Выполняем шаблон
return tmpl.Execute(w, pkg)
}

View File

@@ -1,39 +0,0 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// 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 gen
import (
"strings"
"text/template"
)
// Определяем переменную funcs типа template.FuncMap, которая будет использоваться для
// предоставления пользовательских функций в шаблонах
var funcs = template.FuncMap{
// Функция "tolower" использует strings.ToLower
// для преобразования строки в нижний регистр
"tolower": strings.ToLower,
// Функция "firstchar" — это лямбда-функция, которая берет строку
// и возвращает её первый символ
"firstchar": func(s string) string {
return s[:1]
},
}

View File

@@ -1,133 +0,0 @@
# This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
# It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
#
# 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/>.
# Generated from AUR package: {{.Name}}
# Package type: {{.PackageType}}
# AUR votes: {{.NumVotes}} | Popularity: {{printf "%.2f" .Popularity}}
# Original maintainer: {{.Maintainer}}
# Adapted for ALR by automation
name='{{.Name}}'
version='{{.Version}}'
release='1'
desc='{{.Description}}'
{{if ne .Description ""}}desc_ru='{{.Description}}'{{end}}
homepage='{{.URL}}'
maintainer="Евгений Храмов <xpamych@yandex.ru> (imported from AUR)"
{{if ne .Description ""}}maintainer_ru="Евгений Храмов <xpamych@yandex.ru> (импортирован из AUR)"{{end}}
architectures=({{.ArchitecturesString}})
license=({{.LicenseString}})
{{if .Provides}}provides=({{range .Provides}}'{{.}}' {{end}}){{end}}
{{if .Conflicts}}conflicts=({{range .Conflicts}}'{{.}}' {{end}}){{end}}
{{if .Replaces}}replaces=({{range .Replaces}}'{{.}}' {{end}}){{end}}
# Базовые зависимости
{{if .DependsString}}deps=({{.DependsString}}){{else}}deps=(){{end}}
{{if .MakeDependsString}}build_deps=({{.MakeDependsString}}){{else}}build_deps=(){{end}}
# Зависимости для конкретных дистрибутивов (адаптируйте под нужды пакета)
{{if .DependsString}}deps_arch=({{.DependsString}})
deps_debian=({{.DependsString}})
deps_altlinux=({{.DependsString}})
deps_alpine=({{.DependsString}}){{end}}
{{if and .MakeDependsString (ne .PackageType "bin")}}# Зависимости сборки для конкретных дистрибутивов
build_deps_arch=({{.MakeDependsString}})
build_deps_debian=({{.MakeDependsString}})
build_deps_altlinux=({{.MakeDependsString}})
build_deps_alpine=({{.MakeDependsString}}){{end}}
{{if .OptDependsString}}# Опциональные зависимости
opt_deps=(
{{.OptDependsString}}
){{end}}
# Источники из PKGBUILD
sources=({{range .Sources}}"{{.}}" {{end}})
checksums=({{range .Checksums}}'{{.}}' {{end}})
{{if .HasVersion}}# Функция версии для Git-пакетов
version() {
cd "$srcdir/{{.Name}}"
git-version
}
{{end}}
{{if .ScriptsString}}# Дополнительные скрипты
scripts=(
{{.ScriptsString}}
){{end}}
{{if or .PrepareFunc .HasPatches}}prepare() {
cd "$srcdir"{{if .PrepareFunc}}
# Из PKGBUILD:
{{.PrepareFunc}}{{else}}
# Применение патчей и подготовка исходников
# Раскомментируйте и адаптируйте при необходимости:
# patch -p1 < "${scriptdir}/fix.patch"{{end}}
}{{else}}# prepare() {
# cd "$srcdir"
# # Применение патчей и подготовка исходников при необходимости
# # patch -p1 < "${scriptdir}/fix.patch"
# }{{end}}
{{if ne .PackageType "bin"}}build() {
cd "$srcdir"{{if .BuildFunc}}
# Из PKGBUILD:
{{.BuildFunc}}{{else}}
# TODO: Адаптируйте команды сборки под конкретный проект ({{.PackageType}})
{{if eq .PackageType "meson"}}# Для Meson проектов:
meson setup build --prefix=/usr --buildtype=release
ninja -C build -j $(nproc){{else if eq .PackageType "cpp"}}# Для C/C++ проектов:
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j$(nproc){{else if eq .PackageType "go"}}# Для Go проектов:
go build -buildmode=pie -trimpath -ldflags "-s -w" -o {{.Name}}{{else if eq .PackageType "python"}}# Для Python проектов:
python -m build --wheel --no-isolation{{else if eq .PackageType "nodejs"}}# Для Node.js проектов:
npm ci --production
npm run build{{else if eq .PackageType "rust"}}# Для Rust проектов:
cargo build --release --locked{{else if eq .PackageType "git"}}# Для Git проектов (обычно исходный код):
make -j$(nproc){{else}}# Стандартная сборка:
make -j$(nproc){{end}}{{end}}
}{{else}}# Бинарный пакет - сборка не требуется{{end}}
package() {
cd "$srcdir"{{if .PackageFunc}}
# Из PKGBUILD (адаптировано для ALR):
{{.PackageFunc}}
# Автоматически сгенерированные команды установки:
{{.GenerateInstallCommands}}{{else}}
# TODO: Адаптируйте установку файлов под конкретный проект {{.Name}}
{{if eq .PackageType "meson"}}# Для Meson проектов:
meson install -C build --destdir="$pkgdir"{{else if eq .PackageType "cpp"}}# Для C/C++ проектов:
cd build
make DESTDIR="$pkgdir" install{{else if eq .PackageType "go"}}# Для Go проектов:
# Исполняемый файл уже собран в корне{{else if eq .PackageType "python"}}# Для Python проектов:
pip install --root="$pkgdir/" . --no-deps --disable-pip-version-check{{else if eq .PackageType "nodejs"}}# Для Node.js проектов:
npm install -g --prefix="$pkgdir/usr" .{{else if eq .PackageType "rust"}}# Для Rust проектов:
# Исполняемый файл в target/release/{{else if eq .PackageType "bin"}}# Бинарный пакет:
# Файлы уже распакованы{{else}}# Стандартная установка:
make DESTDIR="$pkgdir" install{{end}}
# Автоматически сгенерированные команды установки:
{{.GenerateInstallCommands}}{{end}}
}

Some files were not shown because too many files have changed in this diff Show More