Compare commits
	
		
			1 Commits
		
	
	
		
			v0.0.16
			...
			a785df1ec6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a785df1ec6 | 
| @@ -16,12 +16,11 @@ | |||||||
|  |  | ||||||
| name: E2E | name: E2E | ||||||
|  |  | ||||||
| # on: |  | ||||||
| #   push: |  | ||||||
| #     branches: [ main ] |  | ||||||
| #   pull_request: |  | ||||||
| on: | on: | ||||||
|   workflow_dispatch: |   push: | ||||||
|  |     branches: [ main ] | ||||||
|  |   pull_request: | ||||||
|  |  | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   tests: |   tests: | ||||||
| @@ -33,25 +32,18 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - name: Checkout |       - name: Checkout | ||||||
|         uses: https://github.com/actions/checkout@v4 |         uses: https://github.com/actions/checkout@v4 | ||||||
|         with: |  | ||||||
|           fetch-depth: 0 |  | ||||||
|        |        | ||||||
|       - name: Set up Go |       - name: Set up Go | ||||||
|         uses: https://github.com/actions/setup-go@v5 |         uses: https://github.com/actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version: '1.24' |           go-version: '1.24' | ||||||
|           cache: false |  | ||||||
|  |  | ||||||
|       # - name: Cache Podman images |       - name: Start Podman service | ||||||
|       #   uses: actions/cache@v4 |         run: nohup podman system service -t 0 unix:/tmp/podman.sock & | ||||||
|       #   with: |  | ||||||
|       #     path: | |  | ||||||
|       #       ~/.local/share/containers/storage |  | ||||||
|       #       /var/lib/containers/storage |  | ||||||
|       #     key: ${{ runner.os }}-primes             |  | ||||||
|  |  | ||||||
|       - name: Run E2E tests |       - name: Run E2E tests | ||||||
|         env: |         env: | ||||||
|  |           DOCKER_HOST: unix:/tmp/podman.sock | ||||||
|           IGNORE_ROOT_CHECK: 1 |           IGNORE_ROOT_CHECK: 1 | ||||||
|         run: | |         run: | | ||||||
|           make e2e-test |           make e2e-test | ||||||
|   | |||||||
| @@ -19,15 +19,13 @@ name: Pre-commit | |||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [ master ] |     branches: [ main ] | ||||||
|   pull_request: |   pull_request: | ||||||
|  |  | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   pre-commit: |   pre-commit: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     container: |  | ||||||
|       image: docker.gitea.com/runner-images:ubuntu-latest |  | ||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout |       - name: Checkout | ||||||
|   | |||||||
| @@ -1,115 +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: Update version in alr-bin |  | ||||||
|         run: | |  | ||||||
|           # Замените значения в файле с конфигурацией |  | ||||||
|           sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh |  | ||||||
|           sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh |  | ||||||
|  |  | ||||||
|       - name: Install alr |  | ||||||
|         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 |  | ||||||
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -3,12 +3,11 @@ | |||||||
| /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 | *.out | ||||||
|  |  | ||||||
| e2e-tests/alr | e2e-tests/alr | ||||||
| CLAUDE.md |  | ||||||
| commit_msg.txt | commit_msg.txt | ||||||
| @@ -36,14 +36,11 @@ linters: | |||||||
|     - unused |     - unused | ||||||
|     - errcheck |     - errcheck | ||||||
|     - typecheck |     - typecheck | ||||||
|     - wrapcheck | #    - forbidigo | ||||||
|  |  | ||||||
| issues: | issues: | ||||||
|   fix: true |   fix: true | ||||||
|   exclude-rules: |   exclude-rules: | ||||||
|     - linters: |  | ||||||
|         - wrapcheck |  | ||||||
|       path-except: "internal/repos/find.go" |  | ||||||
|     - path: _test\.go |     - path: _test\.go | ||||||
|       linters: |       linters: | ||||||
|         - errcheck |         - errcheck | ||||||
|   | |||||||
| @@ -19,13 +19,13 @@ repos: | |||||||
|     hooks: |     hooks: | ||||||
|       - id: test-coverage |       - id: test-coverage | ||||||
|         name: Run test coverage |         name: Run test coverage | ||||||
|         entry: bash scripts/test-coverage-precommit.sh |         entry: make test-coverage | ||||||
|         language: system |         language: system | ||||||
|         pass_filenames: false |         pass_filenames: false | ||||||
|  |  | ||||||
|       - id: fmt |       - id: fmt | ||||||
|         name: Format code |         name: Format code | ||||||
|         entry: bash scripts/fmt-precommit.sh |         entry: make fmt | ||||||
|         language: system |         language: system | ||||||
|         pass_filenames: false |         pass_filenames: false | ||||||
|  |  | ||||||
| @@ -37,7 +37,6 @@ repos: | |||||||
|  |  | ||||||
|       - id: i18n |       - id: i18n | ||||||
|         name: Update i18n |         name: Update i18n | ||||||
|         entry: bash scripts/i18n-precommit.sh |         entry: make i18n | ||||||
|         language: system |         language: system | ||||||
|         pass_filenames: false |         pass_filenames: false | ||||||
|         always_run: true |  | ||||||
							
								
								
									
										33
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,21 +1,16 @@ | |||||||
| NAME := alr | NAME := alr | ||||||
| GIT_VERSION ?= $(shell git describe --tags ) | GIT_VERSION = $(shell git describe --tags ) | ||||||
| IGNORE_ROOT_CHECK ?= 0 | 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 |  | ||||||
|  |  | ||||||
| CREATE_SYSTEM_RESOURCES ?= 1 |  | ||||||
| ROOT_DIRS := /var/cache/alr /etc/alr |  | ||||||
|  |  | ||||||
| ADD_LICENSE_BIN := go run github.com/google/addlicense@4caba19b7ed7818bb86bc4cd20411a246aa4a524 | 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 | 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 | XGOTEXT_BIN := go run github.com/Tom5521/xgotext@v1.2.0 | ||||||
| @@ -26,36 +21,24 @@ build: check-no-root $(BIN) | |||||||
|  |  | ||||||
| export CGO_ENABLED := 0 | export CGO_ENABLED := 0 | ||||||
| $(BIN): | $(BIN): | ||||||
| ifeq ($(GENERATE),1) |  | ||||||
| 	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 $@ | 	go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@ | ||||||
|  |  | ||||||
| check-no-root: | check-no-root: | ||||||
| 	@if [ "$$IGNORE_ROOT_CHECK" != "1" ] && [ "`whoami`" = "root" ]; then \ | 	@if [[ "$(IGNORE_ROOT_CHECK)" != "1" ]] && [[ "$$(whoami)" == 'root' ]]; then \ | ||||||
| 		echo "This target shouldn't run as root" 1>&2; \ | 		echo "This target shouldn't run as root" 1>&2; \ | ||||||
| 		echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \ | 		echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \ | ||||||
| 		exit 1; \ | 		exit 1; \ | ||||||
| 	fi | 	fi | ||||||
|  |  | ||||||
| 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) | 	setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN) | ||||||
| 	@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,7 +48,7 @@ $(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) | ||||||
|  |  | ||||||
| @@ -88,7 +71,7 @@ i18n: | |||||||
| 	bash scripts/i18n-badge.sh | 	bash scripts/i18n-badge.sh | ||||||
|  |  | ||||||
| test-coverage: | test-coverage: | ||||||
| 	go test -tags=test ./... -v -coverpkg=./... -coverprofile=coverage.out | 	go test ./... -v -coverpkg=./... -coverprofile=coverage.out | ||||||
| 	bash scripts/coverage-badge.sh | 	bash scripts/coverage-badge.sh | ||||||
|  |  | ||||||
| update-deps-cve: | update-deps-cve: | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								README.md
									
									
									
									
									
								
							| @@ -20,10 +20,10 @@ 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://plemya-x.ru/alr/install.sh>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. | ||||||
|  |  | ||||||
| ### Сборка из исходного кода | ### Сборка из исходного кода | ||||||
|  |  | ||||||
| @@ -44,7 +44,7 @@ ALR был создан потому, что упаковка программн | |||||||
|  |  | ||||||
| ## Документация | ## Документация | ||||||
|  |  | ||||||
| Документация находится в [Wiki](https://alr.plemya-x.ru/wiki/ALR). | Документация находится в [Wiki](https://disc.plemya-x.ru/c/alr/wiki-alr). | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| @@ -52,21 +52,15 @@ ALR был создан потому, что упаковка программн | |||||||
|  |  | ||||||
| Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.  | Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.  | ||||||
|  |  | ||||||
| Например, репозиторий с ALR [alr-default](https://gitea.plemya-x.ru/Plemya-x/alr-default.git) | Например, репозиторий [Plemya-x/alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так: | ||||||
| ``` | ``` | ||||||
| alr repo add alr-default https://gitea.plemya-x.ru/Plemya-x/alr-default.git | alr addrepo --name alr-repo --url https://gitea.plemya-x.ru/Plemya-x/alr-repo.git | ||||||
| ``` |  | ||||||
| Репозиторий пакетов [alr-repo](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 | ||||||
|  |  | ||||||
| Telegram - https://t.me/plemyakh | Telegram - https://t.me/plemyakh | ||||||
|  |  | ||||||
| ## Спасибы | ## Спасибы | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
|     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> |     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||||
|         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> |         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> | ||||||
|         <text x="33.5" y="14">coverage</text> |         <text x="33.5" y="14">coverage</text> | ||||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">18.9%</text> |         <text x="86" y="15" fill="#010101" fill-opacity=".3">17.0%</text> | ||||||
|         <text x="86" y="14">18.9%</text> |         <text x="86" y="14">17.0%</text> | ||||||
|     </g> |     </g> | ||||||
| </svg> | </svg> | ||||||
|   | |||||||
| Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B | 
| @@ -12,7 +12,7 @@ | |||||||
|     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> |     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||||
|         <text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text> |         <text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text> | ||||||
|         <text x="37" y="14">ru translate</text> |         <text x="37" y="14">ru translate</text> | ||||||
|         <text x="100" y="15" fill="#010101" fill-opacity=".3">100.00%</text> |         <text x="100" y="15" fill="#010101" fill-opacity=".3">96.00%</text> | ||||||
|         <text x="100" y="14">100.00%</text> |         <text x="100" y="14">96.00%</text> | ||||||
|     </g> |     </g> | ||||||
| </svg> | </svg> | ||||||
|   | |||||||
| Before Width: | Height: | Size: 942 B After Width: | Height: | Size: 940 B | 
							
								
								
									
										47
									
								
								build.go
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								build.go
									
									
									
									
									
								
							| @@ -23,16 +23,17 @@ import ( | |||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func BuildCmd() *cli.Command { | func BuildCmd() *cli.Command { | ||||||
| @@ -72,6 +73,12 @@ func BuildCmd() *cli.Command { | |||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			wd, wdCleanup, err := Mount(wd) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer wdCleanup() | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  |  | ||||||
| 			deps, err := appbuilder. | 			deps, err := appbuilder. | ||||||
| @@ -90,7 +97,7 @@ func BuildCmd() *cli.Command { | |||||||
| 			var script string | 			var script string | ||||||
| 			var packages []string | 			var packages []string | ||||||
|  |  | ||||||
| 			var res []*build.BuiltDep | 			var res *build.BuildResult | ||||||
|  |  | ||||||
| 			var scriptArgs *build.BuildPackageFromScriptArgs | 			var scriptArgs *build.BuildPackageFromScriptArgs | ||||||
| 			var dbArgs *build.BuildPackageFromDbArgs | 			var dbArgs *build.BuildPackageFromDbArgs | ||||||
| @@ -126,7 +133,15 @@ func BuildCmd() *cli.Command { | |||||||
| 				// TODO: handle multiple packages | 				// TODO: handle multiple packages | ||||||
| 				packageInput := c.String("package") | 				packageInput := c.String("package") | ||||||
|  |  | ||||||
| 				pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageInput}) | 				arr := strings.Split(packageInput, "/") | ||||||
|  | 				var packageSearch string | ||||||
|  | 				if len(arr) == 2 { | ||||||
|  | 					packageSearch = arr[1] | ||||||
|  | 				} else { | ||||||
|  | 					packageSearch = arr[0] | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageSearch}) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return cliutils.FormatCliExit("failed to find pkgs", err) | 					return cliutils.FormatCliExit("failed to find pkgs", err) | ||||||
| 				} | 				} | ||||||
| @@ -150,9 +165,19 @@ func BuildCmd() *cli.Command { | |||||||
| 				return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil) | 				return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			if scriptArgs != nil { | ||||||
|  | 				scriptFile := filepath.Base(scriptArgs.Script) | ||||||
|  | 				newScriptDir, scriptDirCleanup, err := Mount(filepath.Dir(scriptArgs.Script)) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				defer scriptDirCleanup() | ||||||
|  | 				scriptArgs.Script = filepath.Join(newScriptDir, scriptFile) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			installer, installerClose, err := build.GetSafeInstaller() | 			installer, installerClose, err := build.GetSafeInstaller() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -160,7 +185,9 @@ func BuildCmd() *cli.Command { | |||||||
| 			} | 			} | ||||||
| 			defer installerClose() | 			defer installerClose() | ||||||
|  |  | ||||||
|  | 			if err := utils.ExitIfCantSetNoNewPrivs(); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -195,9 +222,9 @@ func BuildCmd() *cli.Command { | |||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error building package"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error building package"), err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			for _, pkg := range res { | 			for _, pkgPath := range res.PackagePaths { | ||||||
| 				name := filepath.Base(pkg.Path) | 				name := filepath.Base(pkgPath) | ||||||
| 				err = osutils.Move(pkg.Path, filepath.Join(wd, name)) | 				err = osutils.Move(pkgPath, filepath.Join(wd, name)) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err) | 					return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err) | ||||||
| 				} | 				} | ||||||
|   | |||||||
							
								
								
									
										236
									
								
								config.go
									
									
									
									
									
								
							
							
						
						
									
										236
									
								
								config.go
									
									
									
									
									
								
							| @@ -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 |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| alr-repo/foo-pkg 1.0.0-1 |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| alr-repo/bar-pkg 1.0.0-1 |  | ||||||
| alr-repo/foo-pkg 1.0.0-1 |  | ||||||
| @@ -19,24 +19,54 @@ | |||||||
| package e2etests_test | package e2etests_test | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/efficientgo/e2e" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EAlrAddRepo(t *testing.T) { | func TestE2EAlrAddRepo(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"add-repo-remove-repo", | 		"add-repo-remove-repo", | ||||||
| 		COMMON_SYSTEMS, | 		COMMON_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "addrepo", "--name", "alr-repo", "--url", "https://gitea.plemya-x.ru/Plemya-x/alr-repo.git") | 			err := r.Exec(e2e.NewCommand( | ||||||
| 			execShouldNoError(t, r, "bash", "-c", "cat /etc/alr/alr.toml") | 				"sudo", | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "removerepo", "--name", "alr-repo") | 				"alr", | ||||||
|  | 				"addrepo", | ||||||
|  | 				"--name", | ||||||
|  | 				"alr-repo", | ||||||
|  | 				"--url", | ||||||
|  | 				"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
| 			r.Command("bash", "-c", "cat /etc/alr/alr.toml"). | 			err = r.Exec(e2e.NewCommand( | ||||||
| 				ExpectStdoutContains("repo = []"). | 				"bash", | ||||||
| 				Run(t) | 				"-c", | ||||||
|  | 				"cat /etc/alr/alr.toml", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sudo", | ||||||
|  | 				"alr", | ||||||
|  | 				"removerepo", | ||||||
|  | 				"--name", | ||||||
|  | 				"alr-repo", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			var buf bytes.Buffer | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"bash", | ||||||
|  | 				"-c", | ||||||
|  | 				"cat /etc/alr/alr.toml", | ||||||
|  | 			), e2e.WithExecOptionStdout(&buf)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  | 			assert.Contains(t, buf.String(), "rootCmd") | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,16 +21,20 @@ package e2etests_test | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/alecthomas/assert/v2" | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EBashCompletion(t *testing.T) { | func TestE2EBashCompletion(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"bash-completion", | 		"bash-completion", | ||||||
| 		COMMON_SYSTEMS, | 		COMMON_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			execShouldNoError(t, r, "alr", "install", "--generate-bash-completion") | 			err := r.Exec(e2e.NewCommand( | ||||||
|  | 				"alr", "install", "--generate-bash-completion", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,13 +19,85 @@ | |||||||
| package e2etests_test | package e2etests_test | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/sha256" | ||||||
|  | 	"encoding/hex" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/efficientgo/e2e" | ||||||
| 	"go.alt-gnome.ru/capytest/providers/podman" | 	"github.com/stretchr/testify/assert" | ||||||
|  |  | ||||||
|  | 	expect "github.com/tailscale/goexpect" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | // DebugWriter оборачивает io.Writer и логирует все записываемые данные. | ||||||
|  | type DebugWriter struct { | ||||||
|  | 	prefix string | ||||||
|  | 	writer io.Writer | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *DebugWriter) Write(p []byte) (n int, err error) { | ||||||
|  | 	log.Printf("%s: Writing data: %q", d.prefix, p) // Логируем данные | ||||||
|  | 	return d.writer.Write(p) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DebugReader оборачивает io.Reader и логирует все читаемые данные. | ||||||
|  | type DebugReader struct { | ||||||
|  | 	prefix string | ||||||
|  | 	reader io.Reader | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *DebugReader) Read(p []byte) (n int, err error) { | ||||||
|  | 	n, err = d.reader.Read(p) | ||||||
|  | 	if n > 0 { | ||||||
|  | 		log.Printf("%s: Read data: %q", d.prefix, p[:n]) // Логируем данные | ||||||
|  | 	} | ||||||
|  | 	return n, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func e2eSpawn(runnable e2e.Runnable, command e2e.Command, timeout time.Duration, opts ...expect.Option) (expect.Expecter, <-chan error, error, *io.PipeWriter) { | ||||||
|  | 	resCh := make(chan error) | ||||||
|  |  | ||||||
|  | 	// Создаем pipe для stdin и stdout | ||||||
|  | 	stdinReader, stdinWriter := io.Pipe() | ||||||
|  | 	stdoutReader, stdoutWriter := io.Pipe() | ||||||
|  |  | ||||||
|  | 	debugStdinReader := &DebugReader{prefix: "STDIN", reader: stdinReader} | ||||||
|  | 	debugStdoutWriter := &DebugWriter{prefix: "STDOUT", writer: stdoutWriter} | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		err := runnable.Exec( | ||||||
|  | 			command, | ||||||
|  | 			e2e.WithExecOptionStdout(debugStdoutWriter), | ||||||
|  | 			e2e.WithExecOptionStdin(debugStdinReader), | ||||||
|  | 			e2e.WithExecOptionStderr(debugStdoutWriter), | ||||||
|  | 		) | ||||||
|  |  | ||||||
|  | 		resCh <- err | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	exp, chnErr, err := expect.SpawnGeneric(&expect.GenOptions{ | ||||||
|  | 		In:  stdinWriter, | ||||||
|  | 		Out: stdoutReader, | ||||||
|  | 		Wait: func() error { | ||||||
|  | 			return <-resCh | ||||||
|  | 		}, | ||||||
|  | 		Close: func() error { | ||||||
|  | 			stdinWriter.Close() | ||||||
|  | 			stdoutReader.Close() | ||||||
|  | 			return nil | ||||||
|  | 		}, | ||||||
|  | 		Check: func() bool { return true }, | ||||||
|  | 	}, timeout, expect.Verbose(true), expect.VerboseWriter(os.Stdout)) | ||||||
|  |  | ||||||
|  | 	return exp, chnErr, err, stdinWriter | ||||||
|  | } | ||||||
|  |  | ||||||
| var ALL_SYSTEMS []string = []string{ | var ALL_SYSTEMS []string = []string{ | ||||||
| 	"ubuntu-24.04", | 	"ubuntu-24.04", | ||||||
| 	"alt-sisyphus", | 	"alt-sisyphus", | ||||||
| @@ -49,48 +121,77 @@ var COMMON_SYSTEMS []string = []string{ | |||||||
| 	"ubuntu-24.04", | 	"ubuntu-24.04", | ||||||
| } | } | ||||||
|  |  | ||||||
| func execShouldNoError(t *testing.T, r capytest.Runner, cmd string, args ...string) { | func init() { | ||||||
| 	t.Helper() | 	for _, id := range ALL_SYSTEMS { | ||||||
| 	r.Command(cmd, args...).ExpectSuccess().Run(t) | 		buildAlrTestImage(id) | ||||||
| } |  | ||||||
|  |  | ||||||
| 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) |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func buildAlrTestImage(id string) { | ||||||
|  | 	cmd := exec.Command( | ||||||
|  | 		"docker", | ||||||
|  | 		"build", | ||||||
|  | 		"-t", fmt.Sprintf("alr-testimage-%s", id), | ||||||
|  | 		"-f", fmt.Sprintf("images/Dockerfile.%s", id), | ||||||
|  | 		".", | ||||||
|  | 	) | ||||||
|  | 	cmd.Stdout = os.Stdout | ||||||
|  | 	cmd.Stderr = os.Stderr | ||||||
|  | 	err := cmd.Run() | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("Error:", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testing.T, runnable e2e.Runnable)) { | ||||||
|  | 	t.Run(name, func(t *testing.T) { | ||||||
|  | 		for _, id := range ids { | ||||||
|  | 			t.Run(id, func(t *testing.T) { | ||||||
|  | 				t.Parallel() | ||||||
|  | 				dockerName := fmt.Sprintf("alr-test-%s-%s", name, id) | ||||||
|  | 				hash := sha256.New() | ||||||
|  | 				hash.Write([]byte(dockerName)) | ||||||
|  | 				hashSum := hash.Sum(nil) | ||||||
|  | 				hashString := hex.EncodeToString(hashSum) | ||||||
|  | 				truncatedHash := hashString[:8] | ||||||
|  | 				e, err := e2e.New(e2e.WithVerbose(), e2e.WithName(fmt.Sprintf("alr-%s", truncatedHash))) | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				t.Cleanup(e.Close) | ||||||
|  | 				imageId := fmt.Sprintf("alr-testimage-%s", id) | ||||||
|  | 				runnable := e.Runnable(dockerName).Init( | ||||||
|  | 					e2e.StartOptions{ | ||||||
|  | 						Image:   imageId, | ||||||
|  | 						Volumes: []string{ | ||||||
|  | 							// "./alr:/usr/bin/alr", | ||||||
|  | 						}, | ||||||
|  | 						Privileged: true, | ||||||
|  | 					}, | ||||||
|  | 				) | ||||||
|  | 				assert.NoError(t, e2e.StartAndWaitReady(runnable)) | ||||||
|  | 				f(t, runnable) | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func simpleExec(t *testing.T, r e2e.Runnable, cmd string, args ...string) { | ||||||
|  | 	err := r.Exec(e2e.NewCommand(cmd, args...)) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expects []expect.Batcher) { | ||||||
|  | 	exp, _, err, _ := e2eSpawn( | ||||||
|  | 		r, | ||||||
|  | 		e2e.NewCommand("/bin/bash"), 25*time.Second, | ||||||
|  | 		expect.Verbose(true), | ||||||
|  | 	) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	_, err = exp.ExpectBatch( | ||||||
|  | 		expects, | ||||||
|  | 		timeout, | ||||||
|  | 	) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const REPO_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git" | ||||||
|   | |||||||
| @@ -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'") |  | ||||||
| 		}, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| @@ -20,15 +20,24 @@ package e2etests_test | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/efficientgo/e2e" | ||||||
|  | 	expect "github.com/tailscale/goexpect" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EAlrFix(t *testing.T) { | func TestE2EAlrFix(t *testing.T) { | ||||||
| 	runMatrixSuite(t, "run-fix", COMMON_SYSTEMS, func(t *testing.T, r capytest.Runner) { | 	dockerMultipleRun( | ||||||
| 		r.Command("alr", "fix"). | 		t, | ||||||
| 			ExpectStderrContains("--> Done"). | 		"run-fix", | ||||||
| 			ExpectSuccess(). | 		COMMON_SYSTEMS, | ||||||
| 			Run(t) | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 	}) | 			runTestCommands(t, r, time.Second*30, []expect.Batcher{ | ||||||
|  | 				&expect.BSnd{S: "alr fix\n"}, | ||||||
|  | 				&expect.BExp{R: `--> Done`}, | ||||||
|  | 				&expect.BSnd{S: "echo $?\n"}, | ||||||
|  | 				&expect.BExp{R: `^0\n$`}, | ||||||
|  | 			}) | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,18 +21,36 @@ package e2etests_test | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/alecthomas/assert/v2" | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EGroupAndSummaryField(t *testing.T) { | func TestE2EGroupAndSummaryField(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"group-and-summary-field", | 		"group-and-summary-field", | ||||||
| 		RPM_SYSTEMS, | 		RPM_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			defaultPrepare(t, r) | 			err := r.Exec(e2e.NewCommand( | ||||||
| 			execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group.Resolved}}\" | grep ^System/Base$") | 				"sudo", | ||||||
| 			execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary.Resolved}}\" | grep \"^Custom summary$\"") | 				"alr", | ||||||
|  | 				"addrepo", | ||||||
|  | 				"--name", | ||||||
|  | 				"alr-repo", | ||||||
|  | 				"--url", | ||||||
|  | 				"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group}}\" | grep ^System/Base$", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary}}\" | grep \"^Custom summary$\"", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								e2e-tests/images/Dockerfile.alpine
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								e2e-tests/images/Dockerfile.alpine
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | FROM alpine:latest | ||||||
|  | RUN adduser -s /bin/bash alr-user | ||||||
|  | USER alr-user | ||||||
|  | ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||||
							
								
								
									
										6
									
								
								e2e-tests/images/Dockerfile.alt-sisyphus
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								e2e-tests/images/Dockerfile.alt-sisyphus
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | FROM registry.altlinux.org/sisyphus/alt:latest | ||||||
|  | RUN apt-get update && apt-get install -y ca-certificates rpm-build | ||||||
|  | RUN useradd -m -s /bin/bash alr-user | ||||||
|  | USER alr-user | ||||||
|  | WORKDIR /home/alr-user | ||||||
|  | ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||||
							
								
								
									
										4
									
								
								e2e-tests/images/Dockerfile.archlinux
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								e2e-tests/images/Dockerfile.archlinux
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | FROM archlinux:latest | ||||||
|  | RUN useradd -m -s /bin/bash alr-user | ||||||
|  | USER alr-user | ||||||
|  | ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||||
							
								
								
									
										18
									
								
								e2e-tests/images/Dockerfile.fedora-41
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								e2e-tests/images/Dockerfile.fedora-41
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | FROM fedora:41 | ||||||
|  | RUN dnf install -y ca-certificates sudo rpm-build bindfs | ||||||
|  | RUN <<EOF | ||||||
|  |     useradd -m -s /bin/bash -G wheel user | ||||||
|  |     echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/user | ||||||
|  |     chmod 0440 /etc/sudoers.d/user | ||||||
|  |  | ||||||
|  |     useradd -m -s /bin/bash alr | ||||||
|  |     mkdir -p /var/cache/alr /etc/alr | ||||||
|  |     chown alr:alr /var/cache/alr /etc/alr | ||||||
|  | EOF | ||||||
|  | COPY ./alr /usr/bin | ||||||
|  | RUN <<EOF | ||||||
|  |     setcap cap_setuid,cap_setgid+ep /usr/bin/alr | ||||||
|  | EOF | ||||||
|  | USER user | ||||||
|  | WORKDIR /home/user | ||||||
|  | ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||||
							
								
								
									
										4
									
								
								e2e-tests/images/Dockerfile.opensuse-leap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								e2e-tests/images/Dockerfile.opensuse-leap
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | FROM opensuse/leap:latest | ||||||
|  | RUN useradd -m -s /bin/bash alr-user | ||||||
|  | USER alr-user | ||||||
|  | ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||||
							
								
								
									
										4
									
								
								e2e-tests/images/Dockerfile.redos-8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								e2e-tests/images/Dockerfile.redos-8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | FROM registry.red-soft.ru/ubi8/ubi:latest | ||||||
|  | RUN useradd -m -s /bin/bash alr-user | ||||||
|  | USER alr-user | ||||||
|  | ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||||
							
								
								
									
										17
									
								
								e2e-tests/images/Dockerfile.ubuntu-24.04
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								e2e-tests/images/Dockerfile.ubuntu-24.04
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | FROM ubuntu:24.10 | ||||||
|  | RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates sudo libcap2-bin | ||||||
|  | RUN <<EOF | ||||||
|  |     useradd -m -s /bin/bash user | ||||||
|  |     echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/user | ||||||
|  |     chmod 0440 /etc/sudoers.d/user  | ||||||
|  |      | ||||||
|  |     useradd -m -s /bin/bash alr | ||||||
|  |     mkdir -p /var/cache/alr /etc/alr | ||||||
|  |     chown alr:alr /var/cache/alr /etc/alr | ||||||
|  | EOF | ||||||
|  | COPY ./alr /usr/bin | ||||||
|  | RUN <<EOF | ||||||
|  |     setcap cap_setuid,cap_setgid+ep /usr/bin/alr | ||||||
|  | EOF | ||||||
|  | USER user | ||||||
|  | ENTRYPOINT ["tail", "-f", "/dev/null"] | ||||||
| @@ -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) |  | ||||||
| 		}, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| @@ -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}+alr-{repo}", |  | ||||||
| 		COMMON_SYSTEMS, |  | ||||||
| 		func(t *testing.T, r capytest.Runner) { |  | ||||||
| 			t.Parallel() |  | ||||||
| 			defaultPrepare(t, r) |  | ||||||
|  |  | ||||||
| 			r.Command("sudo", "alr", "in", fmt.Sprintf("foo-pkg+alr-%s", REPO_NAME_FOR_E2E_TESTS)). |  | ||||||
| 				ExpectSuccess(). |  | ||||||
| 				Run(t) |  | ||||||
|  |  | ||||||
| 			r.Command("sudo", "alr", "in", fmt.Sprintf("bar-pkg+alr-%s", "NOT_REPO_NAME_FOR_E2E_TESTS")). |  | ||||||
| 				ExpectFailure(). |  | ||||||
| 				Run(t) |  | ||||||
| 		}, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| @@ -21,20 +21,31 @@ package e2etests_test | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/alecthomas/assert/v2" | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EIssue32Interactive(t *testing.T) { | func TestE2EIssue32Interactive(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"issue-32-interactive", | 		"issue-32-interactive", | ||||||
| 		COMMON_SYSTEMS, | 		COMMON_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			execShouldNoError(t, r, "alr", "--interactive=false", "remove", "ca-certificates") | 			assert.NoError(t, r.Exec(e2e.NewCommand( | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "remove", "openssl") | 				"sudo", "alr", "--interactive=false", "remove", "ca-certificates", | ||||||
| 			execShouldNoError(t, r, "alr", "fix") | 			))) | ||||||
| 			execShouldNoError(t, r, "sudo", "apt-get", "update") |  | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "install", "ca-certificates") | 			assert.NoError(t, r.Exec(e2e.NewCommand( | ||||||
|  | 				"sudo", "alr", "--interactive=false", "remove", "openssl", | ||||||
|  | 			))) | ||||||
|  |  | ||||||
|  | 			assert.NoError(t, r.Exec(e2e.NewCommand( | ||||||
|  | 				"alr", "fix", | ||||||
|  | 			))) | ||||||
|  |  | ||||||
|  | 			assert.NoError(t, r.Exec(e2e.NewCommand( | ||||||
|  | 				"sudo", "alr", "--interactive=false", "install", "ca-certificates", | ||||||
|  | 			))) | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,20 +21,61 @@ package e2etests_test | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/alecthomas/assert/v2" | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EIssue41AutoreqSkiplist(t *testing.T) { | func TestE2EIssue41AutoreqSkiplist(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"issue-41-autoreq-skiplist", | 		"issue-41-autoreq-skiplist", | ||||||
| 		AUTOREQ_AUTOPROV_SYSTEMS, | 		AUTOREQ_AUTOPROV_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			defaultPrepare(t, r) | 			err := r.Exec(e2e.NewCommand( | ||||||
| 			execShouldNoError(t, r, "alr", "build", "-p", "alr-repo/test-autoreq-autoprov") | 				"sudo", | ||||||
| 			execShouldNoError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/sh$\"") | 				"alr", | ||||||
| 			execShouldError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/bash$\"") | 				"addrepo", | ||||||
| 			execShouldError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/zsh$\"") | 				"--name", | ||||||
|  | 				"alr-repo", | ||||||
|  | 				"--url", | ||||||
|  | 				"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"alr", | ||||||
|  | 				"ref", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"alr", | ||||||
|  | 				"build", | ||||||
|  | 				"-p", | ||||||
|  | 				"alr-repo/test-autoreq-autoprov", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sh", | ||||||
|  | 				"-c", | ||||||
|  | 				"rpm -qp --requires *.rpm | grep \"^/bin/sh$\"", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sh", | ||||||
|  | 				"-c", | ||||||
|  | 				"rpm -qp --requires *.rpm | grep \"^/bin/bash$\"", | ||||||
|  | 			)) | ||||||
|  | 			assert.Error(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sh", | ||||||
|  | 				"-c", | ||||||
|  | 				"rpm -qp --requires *.rpm | grep \"^/bin/zsh$\"", | ||||||
|  | 			)) | ||||||
|  | 			assert.Error(t, err) | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,19 +21,36 @@ package e2etests_test | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/alecthomas/assert/v2" | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EIssue50InstallMultiple(t *testing.T) { | func TestE2EIssue50InstallMultiple(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"issue-50-install-multiple", | 		"issue-50-install-multiple", | ||||||
| 		COMMON_SYSTEMS, | 		COMMON_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			defaultPrepare(t, r) | 			err := r.Exec(e2e.NewCommand( | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg") | 				"sudo", | ||||||
| 			execShouldNoError(t, r, "cat", "/opt/foo") | 				"alr", | ||||||
| 			execShouldNoError(t, r, "cat", "/opt/bar") | 				"addrepo", | ||||||
|  | 				"--name", | ||||||
|  | 				"alr-repo", | ||||||
|  | 				"--url", | ||||||
|  | 				"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sudo", "alr", "in", "foo-pkg", "bar-pkg", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand("cat", "/opt/foo")) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  | 			err = r.Exec(e2e.NewCommand("cat", "/opt/bar")) | ||||||
|  | 			assert.NoError(t, err) | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,17 +21,33 @@ package e2etests_test | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/alecthomas/assert/v2" | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EIssue53LcAllCInfo(t *testing.T) { | func TestE2EIssue53LcAllCInfo(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"issue-53-lc-all-c-info", | 		"issue-53-lc-all-c-info", | ||||||
| 		COMMON_SYSTEMS, | 		COMMON_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			defaultPrepare(t, r) | 			err := r.Exec(e2e.NewCommand( | ||||||
| 			execShouldNoError(t, r, "bash", "-c", "LANG=C alr info foo-pkg") | 				"sudo", | ||||||
|  | 				"alr", | ||||||
|  | 				"addrepo", | ||||||
|  | 				"--name", | ||||||
|  | 				"alr-repo", | ||||||
|  | 				"--url", | ||||||
|  | 				"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"bash", | ||||||
|  | 				"-c", | ||||||
|  | 				"LANG=C alr info alr-bin", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,20 +21,38 @@ package e2etests_test | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/alecthomas/assert/v2" | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EIssue59RmCompletion(t *testing.T) { | func TestE2EIssue59RmCompletion(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"issue-59-rm-completion", | 		"issue-59-rm-completion", | ||||||
| 		COMMON_SYSTEMS, | 		COMMON_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			defaultPrepare(t, r) | 			err := r.Exec(e2e.NewCommand( | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg") | 				"sudo", | ||||||
| 			execShouldNoError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^foo-pkg$") | 				"alr", | ||||||
| 			execShouldNoError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^bar-pkg$") | 				"addrepo", | ||||||
| 			execShouldError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^test-autoreq-autoprov$") | 				"--name", | ||||||
|  | 				"alr-repo", | ||||||
|  | 				"--url", | ||||||
|  | 				"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sudo", "alr", "in", "foo-pkg", "bar-pkg", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^foo-pkg$")) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  | 			err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^bar-pkg$")) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  | 			err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^test-autoreq-autoprov$")) | ||||||
|  | 			assert.Error(t, err) | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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) |  | ||||||
| 		}, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| @@ -21,17 +21,31 @@ package e2etests_test | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/alecthomas/assert/v2" | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EIssue72InstallWithDeps(t *testing.T) { | func TestE2EIssue72InstallWithDeps(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"issue-72-install-with-deps", | 		"issue-72-install-with-deps", | ||||||
| 		COMMON_SYSTEMS, | 		COMMON_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			defaultPrepare(t, r) | 			err := r.Exec(e2e.NewCommand( | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "in", "test-app-with-lib") | 				"sudo", | ||||||
|  | 				"alr", | ||||||
|  | 				"addrepo", | ||||||
|  | 				"--name", | ||||||
|  | 				"alr-repo", | ||||||
|  | 				"--url", | ||||||
|  | 				"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sudo", "alr", "in", "test-app-with-lib", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,23 +21,30 @@ package e2etests_test | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/efficientgo/e2e" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EIssue74Upgradable(t *testing.T) { | func TestE2EIssue74Upgradable(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"issue-74-upgradable", | 		"issue-74-upgradable", | ||||||
| 		COMMON_SYSTEMS, | 		COMMON_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			defaultPrepare(t, r) | 			simpleExec(t, r, "sudo", | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7") | 				"alr", | ||||||
| 			execShouldNoError(t, r, "alr", "ref") | 				"addrepo", | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "in", "bar-pkg") | 				"--name", | ||||||
| 			execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1") | 				"alr-repo", | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "d9a3541561") | 				"--url", | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "ref") | 				REPO_FOR_E2E_TESTS, | ||||||
| 			execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1") | 			) | ||||||
|  | 			simpleExec(t, r, "sudo", "sh", "-c", "sed -i 's/ref = .*/ref = \"bd26236cd7\"/' /etc/alr/alr.toml") | ||||||
|  | 			simpleExec(t, r, "alr", "ref") | ||||||
|  | 			simpleExec(t, r, "sudo", "alr", "in", "bar-pkg") | ||||||
|  | 			simpleExec(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1") | ||||||
|  | 			simpleExec(t, r, "sudo", "sh", "-c", "sed -i 's/ref = .*/ref = \"d9a3541561\"/' /etc/alr/alr.toml") | ||||||
|  | 			simpleExec(t, r, "sudo", "alr", "ref") | ||||||
|  | 			simpleExec(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1") | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,18 +21,42 @@ package e2etests_test | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/alecthomas/assert/v2" | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EIssue75InstallWithDeps(t *testing.T) { | func TestE2EIssue75InstallWithDeps(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"issue-75-ref-specify", | 		"issue-75-ref-specify", | ||||||
| 		COMMON_SYSTEMS, | 		COMMON_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			defaultPrepare(t, r) | 			err := r.Exec(e2e.NewCommand( | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7") | 				"sudo", | ||||||
| 			execShouldNoError(t, r, "sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1") | 				"alr", | ||||||
|  | 				"addrepo", | ||||||
|  | 				"--name", | ||||||
|  | 				"alr-repo", | ||||||
|  | 				"--url", | ||||||
|  | 				"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sudo", "alr", "ref", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			// TODO: replace with alr command when it be added | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sudo", "sh", "-c", "sed -i 's/ref = .*/ref = \"bd26236cd7\"/' /etc/alr/alr.toml", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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") |  | ||||||
| 		}, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| @@ -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") |  | ||||||
| 	}, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| @@ -21,18 +21,39 @@ package e2etests_test | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/efficientgo/e2e" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EIssue81MultiplePackages(t *testing.T) { | func TestE2EIssue81MultiplePackages(t *testing.T) { | ||||||
| 	runMatrixSuite( | 	dockerMultipleRun( | ||||||
| 		t, | 		t, | ||||||
| 		"issue-81-multiple-packages", | 		"issue-81-multiple-packages", | ||||||
| 		COMMON_SYSTEMS, | 		COMMON_SYSTEMS, | ||||||
| 		func(t *testing.T, r capytest.Runner) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			defaultPrepare(t, r) | 			err := r.Exec(e2e.NewCommand( | ||||||
| 			execShouldNoError(t, r, "sudo", "alr", "in", "first-package-with-dashes") | 				"sudo", | ||||||
| 			execShouldNoError(t, r, "cat", "/opt/first-package") | 				"alr", | ||||||
|  | 				"addrepo", | ||||||
|  | 				"--name", | ||||||
|  | 				"alr-repo", | ||||||
|  | 				"--url", | ||||||
|  | 				REPO_FOR_E2E_TESTS, | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sudo", "alr", "ref", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand( | ||||||
|  | 				"sudo", "alr", "in", "first-package-with-dashes", | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = r.Exec(e2e.NewCommand("cat", "/opt/first-package")) | ||||||
|  | 			assert.NoError(t, err) | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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") |  | ||||||
| 		}, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| @@ -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")) |  | ||||||
| 		}, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| @@ -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\"") |  | ||||||
| 		}, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| @@ -20,16 +20,25 @@ package e2etests_test | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"go.alt-gnome.ru/capytest" | 	"github.com/efficientgo/e2e" | ||||||
|  |  | ||||||
|  | 	expect "github.com/tailscale/goexpect" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestE2EAlrVersion(t *testing.T) { | func TestE2EAlrVersion(t *testing.T) { | ||||||
| 	runMatrixSuite(t, "version", COMMON_SYSTEMS, func(t *testing.T, r capytest.Runner) { | 	dockerMultipleRun( | ||||||
| 		r.Command("alr", "version"). | 		t, | ||||||
| 			ExpectStderrRegex(`^v\d+\.\d+\.\d+(?:-\d+-g[a-f0-9]+)?\n$`). | 		"check-version", | ||||||
| 			ExpectStdoutEmpty(). | 		COMMON_SYSTEMS, | ||||||
| 			ExpectSuccess(). | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			Run(t) | 			runTestCommands(t, r, time.Second*10, []expect.Batcher{ | ||||||
| 	}) | 				&expect.BSnd{S: "alr version\n"}, | ||||||
|  | 				&expect.BExp{R: `^v\d+\.\d+\.\d+(?:-\d+-g[a-f0-9]+)?\n$`}, | ||||||
|  | 				&expect.BSnd{S: "echo $?\n"}, | ||||||
|  | 				&expect.BExp{R: `^0\n$`}, | ||||||
|  | 			}) | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										166
									
								
								fix.go
									
									
									
									
									
								
							
							
						
						
									
										166
									
								
								fix.go
									
									
									
									
									
								
							| @@ -20,10 +20,8 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io/fs" |  | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  |  | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| @@ -34,28 +32,14 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // execWithPrivileges выполняет команду напрямую если root или CI, иначе через sudo |  | ||||||
| func execWithPrivileges(name string, args ...string) *exec.Cmd { |  | ||||||
| 	isRoot := os.Geteuid() == 0 |  | ||||||
| 	isCI := os.Getenv("CI") == "true" |  | ||||||
| 	 |  | ||||||
| 	if !isRoot && !isCI { |  | ||||||
| 		// Если не root и не в CI, используем sudo |  | ||||||
| 		allArgs := append([]string{name}, args...) |  | ||||||
| 		return exec.Command("sudo", allArgs...) |  | ||||||
| 	} else { |  | ||||||
| 		// Если root или в CI, запускаем напрямую |  | ||||||
| 		return exec.Command(name, args...) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func FixCmd() *cli.Command { | func FixCmd() *cli.Command { | ||||||
| 	return &cli.Command{ | 	return &cli.Command{ | ||||||
| 		Name:  "fix", | 		Name:  "fix", | ||||||
| 		Usage: gotext.Get("Attempt to fix problems with ALR"), | 		Usage: gotext.Get("Attempt to fix problems with ALR"), | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			// Команда выполняется от текущего пользователя | 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | ||||||
| 			// При необходимости будет запрошен sudo для удаления файлов root | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  |  | ||||||
| @@ -72,126 +56,32 @@ func FixCmd() *cli.Command { | |||||||
|  |  | ||||||
| 			paths := cfg.GetPaths() | 			paths := cfg.GetPaths() | ||||||
|  |  | ||||||
| 			slog.Info(gotext.Get("Clearing cache and temporary directories")) | 			slog.Info(gotext.Get("Clearing cache directory")) | ||||||
|  | 			// Remove all nested directories of paths.CacheDir | ||||||
|  |  | ||||||
| 			// Проверяем, существует ли директория кэша |  | ||||||
| 			dir, err := os.Open(paths.CacheDir) | 			dir, err := os.Open(paths.CacheDir) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				if os.IsNotExist(err) { | 				return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err) | ||||||
| 					// Директория не существует, просто создадим её позже | 			} | ||||||
| 					slog.Info(gotext.Get("Cache directory does not exist, will create it")) | 			defer dir.Close() | ||||||
| 				} else { |  | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err) |  | ||||||
| 				} |  | ||||||
| 			} else { |  | ||||||
| 				defer dir.Close() |  | ||||||
|  |  | ||||||
| 				entries, err := dir.Readdirnames(-1) | 			entries, err := dir.Readdirnames(-1) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for _, entry := range entries { | ||||||
|  | 				err = os.RemoveAll(filepath.Join(paths.CacheDir, entry)) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err) | 					return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), 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 и правами 775 |  | ||||||
| 			err = utils.EnsureTempDirWithRootOwner(tmpDir, 0o775) |  | ||||||
| 			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, 0o775) |  | ||||||
| 			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, 0o775) |  | ||||||
| 			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 { |  | ||||||
| 					fixCmd := execWithPrivileges("chown", "-R", "root:wheel", 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("Rebuilding cache")) | ||||||
|  |  | ||||||
| 			// Пробуем создать директорию кэша | 			err = os.MkdirAll(paths.CacheDir, 0o755) | ||||||
| 			err = os.MkdirAll(paths.CacheDir, 0o775) |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				// Если не получилось, пробуем через sudo с правильными правами для группы wheel | 				return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err) | ||||||
| 				slog.Info(gotext.Get("Creating cache directory with sudo")) |  | ||||||
| 				sudoCmd := execWithPrivileges("mkdir", "-p", paths.CacheDir) |  | ||||||
| 				if sudoErr := sudoCmd.Run(); sudoErr != nil { |  | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err) |  | ||||||
| 				} |  | ||||||
| 				 |  | ||||||
| 				// Устанавливаем права 775 и группу wheel |  | ||||||
| 				chmodCmd := execWithPrivileges("chmod", "775", paths.CacheDir) |  | ||||||
| 				if chmodErr := chmodCmd.Run(); chmodErr != nil { |  | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Unable to set cache directory permissions"), chmodErr) |  | ||||||
| 				} |  | ||||||
| 				 |  | ||||||
| 				chgrpCmd := execWithPrivileges("chgrp", "wheel", paths.CacheDir) |  | ||||||
| 				if chgrpErr := chgrpCmd.Run(); chgrpErr != nil { |  | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Unable to set cache directory group"), chgrpErr) |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			deps, err = appbuilder. | 			deps, err = appbuilder. | ||||||
| @@ -211,23 +101,3 @@ func FixCmd() *cli.Command { | |||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func makeWritableRecursive(path string) error { |  | ||||||
| 	return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error { |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		info, err := d.Info() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		newMode := info.Mode() | 0o200 |  | ||||||
| 		if d.IsDir() { |  | ||||||
| 			newMode |= 0o100 |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return os.Chmod(path, newMode) |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								gen.go
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								gen.go
									
									
									
									
									
								
							| @@ -25,7 +25,7 @@ import ( | |||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/gen" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/gen" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func GenCmd() *cli.Command { | func GenCmd() *cli.Command { | ||||||
| @@ -61,29 +61,6 @@ func GenCmd() *cli.Command { | |||||||
| 					}) | 					}) | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			{ |  | ||||||
| 				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"), |  | ||||||
| 					}) |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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" |  | ||||||
| } |  | ||||||
| @@ -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) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										61
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								go.mod
									
									
									
									
									
								
							| @@ -1,49 +1,47 @@ | |||||||
| module gitea.plemya-x.ru/Plemya-x/ALR | module gitea.plemya-x.ru/Plemya-x/ALR | ||||||
|  |  | ||||||
| go 1.24.4 | go 1.23.0 | ||||||
|  |  | ||||||
|  | toolchain go1.24.2 | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 | 	gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 | ||||||
| 	github.com/AlecAivazis/survey/v2 v2.3.7 | 	github.com/AlecAivazis/survey/v2 v2.3.7 | ||||||
| 	github.com/PuerkitoBio/purell v1.2.0 | 	github.com/PuerkitoBio/purell v1.2.0 | ||||||
|  | 	github.com/alecthomas/assert/v2 v2.2.1 | ||||||
| 	github.com/alecthomas/chroma/v2 v2.9.1 | 	github.com/alecthomas/chroma/v2 v2.9.1 | ||||||
| 	github.com/bmatcuk/doublestar/v4 v4.8.1 | 	github.com/caarlos0/env v3.5.0+incompatible | ||||||
| 	github.com/charmbracelet/bubbles v0.20.0 | 	github.com/charmbracelet/bubbles v0.20.0 | ||||||
| 	github.com/charmbracelet/bubbletea v1.2.4 | 	github.com/charmbracelet/bubbletea v1.2.4 | ||||||
| 	github.com/charmbracelet/lipgloss v1.0.0 | 	github.com/charmbracelet/lipgloss v1.0.0 | ||||||
| 	github.com/charmbracelet/log v0.4.0 | 	github.com/charmbracelet/log v0.4.0 | ||||||
|  | 	github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 | ||||||
| 	github.com/go-git/go-billy/v5 v5.6.0 | 	github.com/go-git/go-billy/v5 v5.6.0 | ||||||
| 	github.com/go-git/go-git/v5 v5.13.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/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/hashicorp/go-hclog v0.14.1 | ||||||
| 	github.com/hashicorp/go-plugin v1.6.3 | 	github.com/hashicorp/go-plugin v1.6.3 | ||||||
| 	github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 | 	github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 | ||||||
| 	github.com/knadh/koanf/parsers/toml/v2 v2.2.0 | 	github.com/jmoiron/sqlx v1.3.5 | ||||||
| 	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/leonelquinteros/gotext v1.7.0 | ||||||
| 	github.com/mattn/go-isatty v0.0.20 | 	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/stretchr/testify v1.10.0 | ||||||
|  | 	github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 | ||||||
| 	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.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9 |  | ||||||
| 	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.36.0 | ||||||
| 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 | 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 | ||||||
| 	golang.org/x/sys v0.33.0 | 	golang.org/x/sys v0.31.0 | ||||||
| 	golang.org/x/text v0.23.0 | 	golang.org/x/text v0.23.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.10.0 | ||||||
| 	xorm.io/xorm v1.3.9 |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| @@ -54,6 +52,7 @@ require ( | |||||||
| 	github.com/Masterminds/sprig/v3 v3.2.3 // indirect | 	github.com/Masterminds/sprig/v3 v3.2.3 // indirect | ||||||
| 	github.com/Microsoft/go-winio v0.6.1 // indirect | 	github.com/Microsoft/go-winio v0.6.1 // indirect | ||||||
| 	github.com/ProtonMail/go-crypto v1.1.3 // indirect | 	github.com/ProtonMail/go-crypto v1.1.3 // indirect | ||||||
|  | 	github.com/alecthomas/repr v0.2.0 // indirect | ||||||
| 	github.com/andybalholm/brotli v1.0.4 // indirect | 	github.com/andybalholm/brotli v1.0.4 // indirect | ||||||
| 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | ||||||
| 	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect | 	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect | ||||||
| @@ -64,7 +63,7 @@ require ( | |||||||
| 	github.com/charmbracelet/harmonica v0.2.0 // indirect | 	github.com/charmbracelet/harmonica v0.2.0 // indirect | ||||||
| 	github.com/charmbracelet/x/ansi v0.4.5 // indirect | 	github.com/charmbracelet/x/ansi v0.4.5 // indirect | ||||||
| 	github.com/charmbracelet/x/term v0.2.1 // indirect | 	github.com/charmbracelet/x/term v0.2.1 // indirect | ||||||
| 	github.com/cloudflare/circl v1.6.1 // indirect | 	github.com/cloudflare/circl v1.3.8 // indirect | ||||||
| 	github.com/connesc/cipherio v0.2.1 // indirect | 	github.com/connesc/cipherio v0.2.1 // 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/creack/pty v1.1.24 // indirect | ||||||
| @@ -73,49 +72,39 @@ require ( | |||||||
| 	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/efficientgo/core v1.0.0-rc.0 // 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/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect | ||||||
| 	github.com/fatih/color v1.7.0 // 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-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/protobuf v1.5.3 // indirect | ||||||
| 	github.com/golang/snappy v0.0.4 // indirect | 	github.com/golang/snappy v0.0.4 // indirect | ||||||
|  | 	github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // 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/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/hashicorp/yamux v0.1.1 // indirect | ||||||
|  | 	github.com/hexops/gotextdiff v1.0.3 // indirect | ||||||
| 	github.com/huandu/xstrings v1.3.3 // indirect | 	github.com/huandu/xstrings v1.3.3 // indirect | ||||||
| 	github.com/imdario/mergo v0.3.16 // indirect | 	github.com/imdario/mergo v0.3.16 // indirect | ||||||
| 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | ||||||
| 	github.com/json-iterator/go v1.1.12 // indirect |  | ||||||
| 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | ||||||
| 	github.com/kevinburke/ssh_config v1.2.0 // indirect | 	github.com/kevinburke/ssh_config v1.2.0 // indirect | ||||||
| 	github.com/klauspost/compress v1.17.11 // indirect | 	github.com/klauspost/compress v1.17.11 // indirect | ||||||
| 	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.13 // 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.16 // indirect | ||||||
| 	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect | 	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect | ||||||
| 	github.com/mitchellh/copystructure v1.2.0 // indirect | 	github.com/mitchellh/copystructure v1.2.0 // indirect | ||||||
| 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | ||||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect |  | ||||||
| 	github.com/modern-go/reflect2 v1.0.2 // indirect |  | ||||||
| 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect | 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect | ||||||
| 	github.com/muesli/cancelreader v0.2.2 // indirect | 	github.com/muesli/cancelreader v0.2.2 // indirect | ||||||
| 	github.com/muesli/termenv v0.15.2 // indirect | 	github.com/muesli/termenv v0.15.2 // indirect | ||||||
| @@ -126,18 +115,12 @@ require ( | |||||||
| 	github.com/pmezard/go-difflib v1.0.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.7 // 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.3.0 // 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 | ||||||
| @@ -149,11 +132,10 @@ require ( | |||||||
| 	golang.org/x/sync v0.12.0 // indirect | 	golang.org/x/sync v0.12.0 // indirect | ||||||
| 	golang.org/x/term v0.30.0 // indirect | 	golang.org/x/term v0.30.0 // indirect | ||||||
| 	golang.org/x/tools v0.23.0 // indirect | 	golang.org/x/tools v0.23.0 // indirect | ||||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect | 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect | ||||||
| 	google.golang.org/grpc v1.67.3 // indirect | 	google.golang.org/grpc v1.58.3 // indirect | ||||||
| 	google.golang.org/protobuf v1.36.1 // 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 +145,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 |  | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										158
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								go.sum
									
									
									
									
									
								
							| @@ -17,8 +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 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8= | ||||||
| gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw= | gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw= | ||||||
| github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= | github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= | ||||||
| @@ -63,10 +61,10 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd | |||||||
| github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= | ||||||
| github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= | ||||||
| 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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= | ||||||
|  | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= | ||||||
| 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= | ||||||
| @@ -75,11 +73,15 @@ 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/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= | ||||||
| github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= | github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= | ||||||
|  | github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs= | ||||||
|  | github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= | ||||||
| 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/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= | ||||||
|  | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||||
| github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= | github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= | ||||||
| github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= | github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= | ||||||
| github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= | github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= | ||||||
| @@ -98,13 +100,12 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR | |||||||
| 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.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI= | ||||||
| github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= | 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/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.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= | ||||||
| github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= | github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= | ||||||
| @@ -120,6 +121,10 @@ 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/efficientgo/core v1.0.0-rc.0 h1:jJoA0N+C4/knWYVZ6GrdHOtDyrg8Y/TR4vFpTaqTsqs= | ||||||
|  | github.com/efficientgo/core v1.0.0-rc.0/go.mod h1:kQa0V74HNYMfuJH6jiPiwNdpWXl4xd/K4tzlrcvYDQI= | ||||||
|  | github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 h1:C/FNIs+MtAJgQYLJ9FX/ACFYyDRuLYoXTmueErrOJyA= | ||||||
|  | github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0/go.mod h1:plsKU0YHE9uX+7utvr7SiDtVBSHJyEfHRO4UnUgDmts= | ||||||
| github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= | github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= | ||||||
| github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= | github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= | ||||||
| github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= | ||||||
| @@ -132,15 +137,6 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= | |||||||
| github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | ||||||
| github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= | ||||||
| github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= | ||||||
| github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= |  | ||||||
| github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= |  | ||||||
| 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 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= | ||||||
| github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= | github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= | ||||||
| github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= | ||||||
| @@ -157,16 +153,10 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi | |||||||
| github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= | github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= | ||||||
| github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= | github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= | ||||||
| github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= | github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= | ||||||
| github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= | ||||||
| github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | ||||||
| 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= | ||||||
| @@ -186,7 +176,6 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW | |||||||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||||||
| github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | ||||||
| github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||||
| github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= |  | ||||||
| github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | ||||||
| github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||||
| github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | ||||||
| @@ -198,7 +187,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ | |||||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
| github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
| github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4= | ||||||
|  | github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= | ||||||
| github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= | ||||||
| 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= | ||||||
| @@ -211,8 +201,8 @@ github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQ | |||||||
| github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= | 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/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/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= | github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= | ||||||
| @@ -239,8 +229,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq | |||||||
| github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | ||||||
| github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= | ||||||
| github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= | ||||||
| github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= |  | ||||||
| github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= |  | ||||||
| github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= | github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= | ||||||
| github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= | github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= | ||||||
| github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||||
| @@ -253,8 +241,10 @@ github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3m | |||||||
| github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= | github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= | ||||||
| github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= | 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/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= | ||||||
| github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= | ||||||
| github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= | ||||||
|  | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= | ||||||
|  | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= | ||||||
| github.com/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= | ||||||
| @@ -270,18 +260,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= | ||||||
| @@ -291,10 +269,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | |||||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||||
| github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8= | github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8= | ||||||
| github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw= | github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw= | ||||||
|  | github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= | ||||||
|  | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | ||||||
| github.com/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/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||||||
| @@ -311,8 +289,11 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei | |||||||
| 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.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | ||||||
| github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | github.com/mattn/go-runewidth v0.0.16/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/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= | ||||||
|  | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | ||||||
| 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= | ||||||
| @@ -325,11 +306,6 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR | |||||||
| github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | ||||||
| github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= | ||||||
| github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | ||||||
| github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |  | ||||||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= |  | ||||||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |  | ||||||
| github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= |  | ||||||
| github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= |  | ||||||
| github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= | ||||||
| github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= | ||||||
| github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | ||||||
| @@ -338,28 +314,33 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= | |||||||
| github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= | ||||||
| github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= | 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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= | ||||||
|  | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | ||||||
| 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/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= | ||||||
| github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= | ||||||
| github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= |  | ||||||
| github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= |  | ||||||
| github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= |  | ||||||
| github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= |  | ||||||
| github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= | github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= | ||||||
| github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= | github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= | ||||||
| github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= | ||||||
| github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= | ||||||
| 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= | ||||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
|  | github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= | ||||||
|  | github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= | ||||||
| github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||||
|  | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= | ||||||
|  | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||||
|  | github.com/prometheus/common v0.36.0 h1:78hJTing+BLYLjhXE+Z2BubeEymH5Lr0/Mt8FKkxxYo= | ||||||
|  | github.com/prometheus/common v0.36.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= | ||||||
|  | github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= | ||||||
|  | github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= | ||||||
| github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= | ||||||
| github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= | ||||||
| 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= | ||||||
| @@ -368,7 +349,6 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ | |||||||
| github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||||||
| github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | github.com/rivo/uniseg v0.4.7/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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= | ||||||
| github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= | 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= | ||||||
| @@ -378,9 +358,8 @@ github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtC | |||||||
| github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= | github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= | ||||||
| github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= | ||||||
| github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= | ||||||
|  | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= | ||||||
| github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= | ||||||
| github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= |  | ||||||
| github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= |  | ||||||
| github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | ||||||
| github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= | github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= | ||||||
| github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= | github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= | ||||||
| @@ -389,31 +368,24 @@ github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+ | |||||||
| 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.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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||||
|  | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
| github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= | github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns= | ||||||
| github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= | github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ= | ||||||
| github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= | github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= | ||||||
| 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= | ||||||
| @@ -432,12 +404,6 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr | |||||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||||
| gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= | gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= | ||||||
| gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= | gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= | ||||||
| go.alt-gnome.ru/capytest v0.0.2 h1:clmvIqmYS86hhA1rsvivSSPpfOFkJTpbn38EQP7I3E8= |  | ||||||
| go.alt-gnome.ru/capytest v0.0.2/go.mod h1:lvxPx3H6h+LPnStBFblgoT2wkjv0wbug3S14troykEg= |  | ||||||
| go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 h1:NST+V5LV/eLgs0p6PsuvfHiZ4UrIWqftCdifO8zgg0g= |  | ||||||
| go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:qiM8LARP+JBZr5mrDoVylOoqjrN0MAzvZ21NR9qMc0Y= |  | ||||||
| go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9 h1:VZclgdJxARvhZ6PIWWW2hQ6Ge4XeE36pzUr/U/y62bE= |  | ||||||
| go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9/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= | ||||||
| @@ -487,7 +453,6 @@ golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= | |||||||
| golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |  | ||||||
| golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||||
| @@ -511,6 +476,8 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr | |||||||
| 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= | ||||||
| golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
| golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
|  | golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= | ||||||
|  | golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= | ||||||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| @@ -521,7 +488,6 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ | |||||||
| golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= | ||||||
| golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | ||||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |  | ||||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| @@ -548,8 +514,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc | |||||||
| 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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.2.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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= | ||||||
| golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | ||||||
| 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= | ||||||
| @@ -610,6 +576,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 | |||||||
| google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||||
| google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= | ||||||
| google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | ||||||
|  | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= | ||||||
|  | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | ||||||
| google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | ||||||
| google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||||
| google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||||
| @@ -623,8 +591,8 @@ 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-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= | ||||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= | ||||||
| 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,8 +600,8 @@ 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.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= | ||||||
| google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s= | google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= | ||||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | 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.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 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= | ||||||
| @@ -644,15 +612,11 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 | |||||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | ||||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||||||
| gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | ||||||
| gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= |  | ||||||
| gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= |  | ||||||
| gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= |  | ||||||
| gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= |  | ||||||
| gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= | ||||||
| gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= | ||||||
| gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |  | ||||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
|  | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||||||
| gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
| @@ -695,7 +659,3 @@ mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY= | |||||||
| rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= | ||||||
| rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= | ||||||
| rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= | ||||||
| xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM= |  | ||||||
| xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= |  | ||||||
| xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU= |  | ||||||
| xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw= |  | ||||||
|   | |||||||
							
								
								
									
										63
									
								
								info.go
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								info.go
									
									
									
									
									
								
							| @@ -23,15 +23,16 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
| 	"github.com/goccy/go-yaml" |  | ||||||
| 	"github.com/jeandeaual/go-locale" | 	"github.com/jeandeaual/go-locale" | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"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" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
|  | 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -47,6 +48,9 @@ func InfoCmd() *cli.Command { | |||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||||
|  | 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
| 			deps, err := appbuilder. | 			deps, err := appbuilder. | ||||||
| @@ -63,14 +67,23 @@ func InfoCmd() *cli.Command { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||||
| 			} | 			} | ||||||
|  | 			defer result.Close() | ||||||
|  |  | ||||||
|  | 			for result.Next() { | ||||||
|  | 				var pkg database.Package | ||||||
|  | 				err = result.StructScan(&pkg) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 			for _, pkg := range result { |  | ||||||
| 				fmt.Println(pkg.Name) | 				fmt.Println(pkg.Name) | ||||||
| 			} | 			} | ||||||
| 			return nil | 			return nil | ||||||
| 		}), | 		}), | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			// Запуск от текущего пользователя | 			if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			args := c.Args() | 			args := c.Args() | ||||||
| 			if args.Len() < 1 { | 			if args.Len() < 1 { | ||||||
| @@ -83,7 +96,6 @@ func InfoCmd() *cli.Command { | |||||||
| 				New(ctx). | 				New(ctx). | ||||||
| 				WithConfig(). | 				WithConfig(). | ||||||
| 				WithDB(). | 				WithDB(). | ||||||
| 				WithDistroInfo(). |  | ||||||
| 				WithRepos(). | 				WithRepos(). | ||||||
| 				Build() | 				Build() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -115,27 +127,34 @@ func InfoCmd() *cli.Command { | |||||||
| 				systemLang = "en" | 				systemLang = "en" | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			info, err := distro.ParseOSRelease(ctx) | 			if !all { | ||||||
| 			if err != nil { | 				info, err := distro.ParseOSRelease(ctx) | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) | 				if err != nil { | ||||||
| 			} | 					return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) | ||||||
| 			names, err = overrides.Resolve( | 				} | ||||||
| 				info, | 				names, err = overrides.Resolve( | ||||||
| 				overrides.DefaultOpts. | 					info, | ||||||
| 					WithLanguages([]string{systemLang}), | 					overrides.DefaultOpts. | ||||||
| 			) | 						WithLanguages([]string{systemLang}), | ||||||
| 			if err != nil { | 				) | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err) | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			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 | 					if err != nil { | ||||||
| 				err = yaml.NewEncoder(os.Stdout, yaml.UseJSONMarshaler(), yaml.OmitEmpty()).Encode(view) | 						return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) | ||||||
| 				if err != nil { | 					} | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) | 				} else { | ||||||
|  | 					err = yaml.NewEncoder(os.Stdout).Encode(pkg) | ||||||
|  | 					if err != nil { | ||||||
|  | 						return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) | ||||||
|  | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				fmt.Println("---") | 				fmt.Println("---") | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								install.go
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								install.go
									
									
									
									
									
								
							| @@ -25,12 +25,13 @@ import ( | |||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func InstallCmd() *cli.Command { | func InstallCmd() *cli.Command { | ||||||
| @@ -51,6 +52,9 @@ func InstallCmd() *cli.Command { | |||||||
| 				return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil) | 				return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			installer, installerClose, err := build.GetSafeInstaller() | 			installer, installerClose, err := build.GetSafeInstaller() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -58,6 +62,9 @@ func InstallCmd() *cli.Command { | |||||||
| 			} | 			} | ||||||
| 			defer installerClose() | 			defer installerClose() | ||||||
|  |  | ||||||
|  | 			if err := utils.ExitIfCantSetNoNewPrivs(); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -91,7 +98,7 @@ func InstallCmd() *cli.Command { | |||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			_, err = builder.InstallPkgs( | 			err = builder.InstallPkgs( | ||||||
| 				ctx, | 				ctx, | ||||||
| 				&build.BuildArgs{ | 				&build.BuildArgs{ | ||||||
| 					Opts: &types.BuildOpts{ | 					Opts: &types.BuildOpts{ | ||||||
| @@ -110,6 +117,9 @@ func InstallCmd() *cli.Command { | |||||||
| 			return nil | 			return nil | ||||||
| 		}), | 		}), | ||||||
| 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||||
|  | 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
| 			deps, err := appbuilder. | 			deps, err := appbuilder. | ||||||
| @@ -126,8 +136,15 @@ func InstallCmd() *cli.Command { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||||
| 			} | 			} | ||||||
|  | 			defer result.Close() | ||||||
|  |  | ||||||
|  | 			for result.Next() { | ||||||
|  | 				var pkg database.Package | ||||||
|  | 				err = result.StructScan(&pkg) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 			for _, pkg := range result { |  | ||||||
| 				fmt.Println(pkg.Name) | 				fmt.Println(pkg.Name) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @@ -173,12 +190,20 @@ func RemoveCmd() *cli.Command { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||||
| 			} | 			} | ||||||
|  | 			defer result.Close() | ||||||
|  |  | ||||||
|  | 			for result.Next() { | ||||||
|  | 				var pkg database.Package | ||||||
|  | 				err = result.StructScan(&pkg) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 			for _, pkg := range result { |  | ||||||
| 				_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] | 				_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] | ||||||
| 				if !ok { | 				if !ok { | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				fmt.Println(pkg.Name) | 				fmt.Println(pkg.Name) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										200
									
								
								internal.go
									
									
									
									
									
								
							
							
						
						
									
										200
									
								
								internal.go
									
									
									
									
									
								
							| @@ -17,8 +17,14 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"os/user" | ||||||
|  | 	"path/filepath" | ||||||
| 	"syscall" | 	"syscall" | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/go-hclog" | 	"github.com/hashicorp/go-hclog" | ||||||
| @@ -26,13 +32,14 @@ import ( | |||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func InternalBuildCmd() *cli.Command { | func InternalBuildCmd() *cli.Command { | ||||||
| @@ -45,6 +52,9 @@ func InternalBuildCmd() *cli.Command { | |||||||
|  |  | ||||||
| 			slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid()) | 			slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid()) | ||||||
|  |  | ||||||
|  | 			if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			cfg := config.New() | 			cfg := config.New() | ||||||
| 			err := cfg.Load() | 			err := cfg.Load() | ||||||
| @@ -74,40 +84,6 @@ func InternalBuildCmd() *cli.Command { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| 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 { | func InternalInstallCmd() *cli.Command { | ||||||
| 	return &cli.Command{ | 	return &cli.Command{ | ||||||
| 		Name:     "_internal-installer", | 		Name:     "_internal-installer", | ||||||
| @@ -116,7 +92,16 @@ func InternalInstallCmd() *cli.Command { | |||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			logger.SetupForGoPlugin() | 			logger.SetupForGoPlugin() | ||||||
|  |  | ||||||
| 			// Запуск от текущего пользователя, повышение прав будет через sudo при необходимости | 			if err := utils.EnsureIsAlrUser(); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Before escalating the rights, we made sure that | ||||||
|  | 			// this is an ALR user, so it looks safe. | ||||||
|  | 			err := utils.EscalateToRootUid() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit("cannot escalate to root", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			deps, err := appbuilder. | 			deps, err := appbuilder. | ||||||
| 				New(c.Context). | 				New(c.Context). | ||||||
| @@ -140,7 +125,7 @@ func InternalInstallCmd() *cli.Command { | |||||||
| 			plugin.Serve(&plugin.ServeConfig{ | 			plugin.Serve(&plugin.ServeConfig{ | ||||||
| 				HandshakeConfig: build.HandshakeConfig, | 				HandshakeConfig: build.HandshakeConfig, | ||||||
| 				Plugins: map[string]plugin.Plugin{ | 				Plugins: map[string]plugin.Plugin{ | ||||||
| 					"installer": &build.InstallerExecutorPlugin{ | 					"installer": &build.InstallerPlugin{ | ||||||
| 						Impl: build.NewInstaller( | 						Impl: build.NewInstaller( | ||||||
| 							manager.Detect(), | 							manager.Detect(), | ||||||
| 						), | 						), | ||||||
| @@ -153,4 +138,143 @@ func InternalInstallCmd() *cli.Command { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func Mount(target string) (string, func(), error) { | ||||||
|  | 	exe, err := os.Executable() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", nil, fmt.Errorf("failed to get executable path: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd := exec.Command(exe, "_internal-temporary-mount", target) | ||||||
|  |  | ||||||
|  | 	stdoutPipe, err := cmd.StdoutPipe() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", nil, fmt.Errorf("failed to get stdout pipe: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	stdinPipe, err := cmd.StdinPipe() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", nil, fmt.Errorf("failed to get stdin pipe: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd.Stderr = os.Stderr | ||||||
|  |  | ||||||
|  | 	if err := cmd.Start(); err != nil { | ||||||
|  | 		return "", nil, fmt.Errorf("failed to start mount: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	scanner := bufio.NewScanner(stdoutPipe) | ||||||
|  | 	var mountPath string | ||||||
|  | 	if scanner.Scan() { | ||||||
|  | 		mountPath = scanner.Text() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := scanner.Err(); err != nil { | ||||||
|  | 		_ = cmd.Process.Kill() | ||||||
|  | 		return "", nil, fmt.Errorf("failed to read mount output: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if mountPath == "" { | ||||||
|  | 		_ = cmd.Process.Kill() | ||||||
|  | 		return "", nil, errors.New("mount failed: no target path returned") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cleanup := func() { | ||||||
|  | 		slog.Debug("cleanup triggered") | ||||||
|  | 		_, _ = fmt.Fprintln(stdinPipe, "") | ||||||
|  | 		_ = cmd.Wait() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return mountPath, cleanup, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func InternalMountCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
|  | 		Name:     "_internal-temporary-mount", | ||||||
|  | 		HideHelp: true, | ||||||
|  | 		Hidden:   true, | ||||||
|  | 		Action: func(c *cli.Context) error { | ||||||
|  | 			logger.SetupForGoPlugin() | ||||||
|  |  | ||||||
|  | 			sourceDir := c.Args().First() | ||||||
|  |  | ||||||
|  | 			u, err := user.Current() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit("cannot get current user", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			_, alrGid, err := utils.GetUidGidAlrUser() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit("cannot get alr user", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if _, err := os.Stat(sourceDir); err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(fmt.Sprintf("cannot read %s", sourceDir), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if err := utils.EnuseIsPrivilegedGroupMember(); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Before escalating the rights, we made sure that | ||||||
|  | 			// 1. user in wheel group | ||||||
|  | 			// 2. user can access sourceDir | ||||||
|  | 			if err := utils.EscalateToRootUid(); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			if err := syscall.Setgid(alrGid); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if err := os.MkdirAll(constants.AlrRunDir, 0o770); err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(fmt.Sprintf("failed to create %s", constants.AlrRunDir), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if err := os.Chown(constants.AlrRunDir, 0, alrGid); err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(fmt.Sprintf("failed to chown %s", constants.AlrRunDir), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			targetDir := filepath.Join(constants.AlrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid())) | ||||||
|  | 			// 0750: owner (root) and group (alr) | ||||||
|  | 			if err := os.MkdirAll(targetDir, 0o750); err != nil { | ||||||
|  | 				return cliutils.FormatCliExit("error creating bindfs target directory", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			//  chown AlrRunDir/mounts/bindfs-* to (root:alr), | ||||||
|  | 			//  so alr user can access dir | ||||||
|  | 			if err := os.Chown(targetDir, 0, alrGid); err != nil { | ||||||
|  | 				return cliutils.FormatCliExit("failed to chown bindfs directory", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			bindfsCmd := exec.Command( | ||||||
|  | 				"bindfs", | ||||||
|  | 				fmt.Sprintf("--map=%s/alr:@%s/@alr", u.Uid, u.Gid), | ||||||
|  | 				sourceDir, | ||||||
|  | 				targetDir, | ||||||
|  | 			) | ||||||
|  |  | ||||||
|  | 			bindfsCmd.Stderr = os.Stderr | ||||||
|  |  | ||||||
|  | 			if err := bindfsCmd.Run(); err != nil { | ||||||
|  | 				return cliutils.FormatCliExit("failed to strart bindfs", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			fmt.Println(targetDir) | ||||||
|  |  | ||||||
|  | 			_, _ = bufio.NewReader(os.Stdin).ReadString('\n') | ||||||
|  |  | ||||||
|  | 			slog.Debug("start unmount", "dir", targetDir) | ||||||
|  |  | ||||||
|  | 			umountCmd := exec.Command("umount", targetDir) | ||||||
|  | 			umountCmd.Stderr = os.Stderr | ||||||
|  | 			if err := umountCmd.Run(); err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(fmt.Sprintf("failed to unmount %s", targetDir), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if err := os.Remove(targetDir); err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(fmt.Sprintf("error removing directory %s", targetDir), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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=") |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -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) |  | ||||||
| } |  | ||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -26,9 +26,9 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type AppDeps struct { | type AppDeps struct { | ||||||
| @@ -123,15 +123,8 @@ func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder { | |||||||
|  |  | ||||||
| 	cfg := b.deps.Cfg | 	cfg := b.deps.Cfg | ||||||
| 	db := b.deps.DB | 	db := b.deps.DB | ||||||
| 	info := b.deps.Info | 	if cfg == nil || db == nil { | ||||||
|  | 		b.err = errors.New("config and db are required before initializing repos") | ||||||
| 	if info == nil { |  | ||||||
| 		b.WithDistroInfo() |  | ||||||
| 		info = b.deps.Info |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if cfg == nil || db == nil || info == nil { |  | ||||||
| 		b.err = errors.New("config, db and info are required before initializing repos") |  | ||||||
| 		return b | 		return b | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -28,8 +28,8 @@ import ( | |||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // YesNoPrompt asks the user a yes or no question, using def as the default answer | // YesNoPrompt asks the user a yes or no question, using def as the default answer | ||||||
| @@ -102,65 +102,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) | 	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 { | ||||||
| 				// Для мультипакетов при установке ВСЕГДА берем все подпакеты без выбора | 				slog.Error(gotext.Get("Error prompting for choice of package")) | ||||||
| 				// Это правильное поведение как для прямой установки, так и для зависимостей | 				os.Exit(1) | ||||||
| 				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 | ||||||
| 	} | 	} | ||||||
| @@ -178,7 +138,7 @@ func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, intera | |||||||
| 	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 | ||||||
|   | |||||||
| @@ -20,163 +20,142 @@ | |||||||
| package config | package config | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  | 	"reflect" | ||||||
|  |  | ||||||
| 	"github.com/goccy/go-yaml" | 	"github.com/caarlos0/env" | ||||||
| 	"github.com/knadh/koanf/providers/confmap" | 	"github.com/pelletier/go-toml/v2" | ||||||
| 	"github.com/knadh/koanf/v2" |  | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type ALRConfig struct { | type ALRConfig struct { | ||||||
| 	cfg   *types.Config | 	cfg   *types.Config | ||||||
| 	paths *Paths | 	paths *Paths | ||||||
|  | } | ||||||
|  |  | ||||||
| 	System *SystemConfig | var defaultConfig = &types.Config{ | ||||||
| 	env    *EnvConfig | 	RootCmd:          "sudo", | ||||||
|  | 	UseRootCmd:       true, | ||||||
|  | 	PagerStyle:       "native", | ||||||
|  | 	IgnorePkgUpdates: []string{}, | ||||||
|  | 	AutoPull:         true, | ||||||
|  | 	Repos:            []types.Repo{}, | ||||||
| } | } | ||||||
|  |  | ||||||
| func New() *ALRConfig { | func New() *ALRConfig { | ||||||
| 	return &ALRConfig{ | 	return &ALRConfig{} | ||||||
| 		System: NewSystemConfig(), |  | ||||||
| 		env:    NewEnvConfig(), |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func defaultConfigKoanf() *koanf.Koanf { | func readConfig(path string) (*types.Config, error) { | ||||||
| 	k := koanf.New(".") | 	file, err := os.Open(path) | ||||||
| 	defaults := map[string]interface{}{ | 	if err != nil { | ||||||
| 		"rootCmd":          "sudo", | 		return nil, err | ||||||
| 		"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 { | 	defer file.Close() | ||||||
| 		panic(k) |  | ||||||
|  | 	config := types.Config{} | ||||||
|  |  | ||||||
|  | 	if err := toml.NewDecoder(file).Decode(&config); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &config, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func mergeStructs(dst, src interface{}) { | ||||||
|  | 	srcVal := reflect.ValueOf(src) | ||||||
|  | 	if srcVal.IsNil() { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	srcVal = srcVal.Elem() | ||||||
|  | 	dstVal := reflect.ValueOf(dst).Elem() | ||||||
|  |  | ||||||
|  | 	for i := range srcVal.NumField() { | ||||||
|  | 		srcField := srcVal.Field(i) | ||||||
|  | 		srcFieldName := srcVal.Type().Field(i).Name | ||||||
|  |  | ||||||
|  | 		dstField := dstVal.FieldByName(srcFieldName) | ||||||
|  | 		if dstField.IsValid() && dstField.CanSet() { | ||||||
|  | 			dstField.Set(srcField) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return k |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *ALRConfig) Load() error { | func (c *ALRConfig) Load() error { | ||||||
| 	config := types.Config{} | 	systemConfig, err := readConfig( | ||||||
|  | 		constants.SystemConfigPath, | ||||||
| 	merged := koanf.New(".") | 	) | ||||||
|  | 	if err != nil { | ||||||
| 	if err := c.System.Load(); err != nil { | 		slog.Debug("Cannot read system config", "err", err) | ||||||
| 		return fmt.Errorf("failed to load system config: %w", err) |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := c.env.Load(); err != nil { | 	config := &types.Config{} | ||||||
| 		return fmt.Errorf("failed to load env config: %w", err) |  | ||||||
|  | 	mergeStructs(config, defaultConfig) | ||||||
|  | 	mergeStructs(config, systemConfig) | ||||||
|  | 	err = env.Parse(config) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	systemK := c.System.koanf() | 	c.cfg = config | ||||||
| 	envK := c.env.koanf() |  | ||||||
|  |  | ||||||
| 	if err := merged.Merge(defaultConfigKoanf()); err != nil { |  | ||||||
| 		return fmt.Errorf("failed to merge default config: %w", err) |  | ||||||
| 	} |  | ||||||
| 	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 = &Paths{} | ||||||
| 	c.paths.UserConfigPath = constants.SystemConfigPath | 	c.paths.UserConfigPath = constants.SystemConfigPath | ||||||
| 	c.paths.CacheDir = constants.SystemCachePath | 	c.paths.CacheDir = constants.SystemCachePath | ||||||
| 	c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo") | 	c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo") | ||||||
| 	c.paths.PkgsDir = filepath.Join(constants.TempDir, "pkgs")  // Перемещаем в /tmp/alr/pkgs | 	c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs") | ||||||
| 	c.paths.DBPath = filepath.Join(c.paths.CacheDir, "alr.db") | 	c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db") | ||||||
|  | 	// c.initPaths() | ||||||
| 	// Проверяем существование кэш-директории, но не пытаемся создать |  | ||||||
| 	if _, err := os.Stat(c.paths.CacheDir); err != nil { |  | ||||||
| 		if !os.IsNotExist(err) { |  | ||||||
| 			return fmt.Errorf("failed to check cache directory: %w", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Выполняем миграцию конфигурации при необходимости |  | ||||||
| 	if err := c.migrateConfig(); err != nil { |  | ||||||
| 		return fmt.Errorf("failed to migrate config: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *ALRConfig) ToYAML() (string, error) { | func (c *ALRConfig) RootCmd() string { | ||||||
| 	data, err := yaml.Marshal(c.cfg) | 	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.cfg.Repos = 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) GetPaths() *Paths { | ||||||
|  | 	return c.paths | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) SaveUserConfig() error { | ||||||
|  | 	f, err := os.Create(c.paths.UserConfigPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return err | ||||||
| 	} | 	} | ||||||
| 	return string(data), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *ALRConfig) migrateConfig() error { | 	return toml.NewEncoder(f).Encode(c.cfg) | ||||||
| 	// Проверяем, существует ли конфигурационный файл |  | ||||||
| 	if _, err := os.Stat(constants.SystemConfigPath); os.IsNotExist(err) { |  | ||||||
| 		// Если файла нет, но конфигурация уже загружена (из defaults или env), |  | ||||||
| 		// создаем файл с настройкой по умолчанию |  | ||||||
| 		needsCreation := false |  | ||||||
| 		 |  | ||||||
| 		// Проверяем, установлена ли переменная окружения ALR_UPDATESYSTEMONUPGRADE |  | ||||||
| 		if os.Getenv("ALR_UPDATESYSTEMONUPGRADE") == "" { |  | ||||||
| 			// Если переменная не установлена, проверяем наличие пакетов ALR |  | ||||||
| 			// чтобы определить, нужно ли включить эту опцию для обновления |  | ||||||
| 			needsCreation = true |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		if needsCreation { |  | ||||||
| 			// Устанавливаем значение false по умолчанию для новой опции |  | ||||||
| 			c.System.SetUpdateSystemOnUpgrade(false) |  | ||||||
| 			// Сохраняем конфигурацию |  | ||||||
| 			if err := c.System.Save(); err != nil { |  | ||||||
| 				// Если не удается сохранить - это не критично, продолжаем работу |  | ||||||
| 				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) 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 } |  | ||||||
|   | |||||||
| @@ -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, "") |  | ||||||
| } |  | ||||||
| @@ -21,10 +21,9 @@ package config | |||||||
|  |  | ||||||
| // Paths contains various paths used by ALR | // Paths contains various paths used by ALR | ||||||
| type Paths struct { | type Paths struct { | ||||||
| 	SystemConfigPath string | 	UserConfigPath string | ||||||
| 	UserConfigPath   string | 	CacheDir       string | ||||||
| 	CacheDir         string | 	RepoDir        string | ||||||
| 	RepoDir          string | 	PkgsDir        string | ||||||
| 	PkgsDir          string | 	DBPath         string | ||||||
| 	DBPath           string |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -19,6 +19,6 @@ package constants | |||||||
| const ( | const ( | ||||||
| 	SystemConfigPath = "/etc/alr/alr.toml" | 	SystemConfigPath = "/etc/alr/alr.toml" | ||||||
| 	SystemCachePath  = "/var/cache/alr" | 	SystemCachePath  = "/var/cache/alr" | ||||||
| 	TempDir          = "/tmp/alr" | 	AlrRunDir        = "/var/run/alr" | ||||||
| 	PrivilegedGroup  = "wheel" | 	PrivilegedGroup  = "wheel" | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -21,23 +21,43 @@ package db | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" |  | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
|  |  | ||||||
|  | 	"github.com/jmoiron/sqlx" | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	_ "modernc.org/sqlite" |  | ||||||
| 	"xorm.io/xorm" |  | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const CurrentVersion = 5 | // CurrentVersion is the current version of the database. | ||||||
|  | // The database is reset if its version doesn't match this. | ||||||
|  | const CurrentVersion = 4 | ||||||
|  |  | ||||||
| type Version struct { | // Package is a ALR package's database representation | ||||||
| 	Version int `xorm:"'version'"` | type Package struct { | ||||||
|  | 	BasePkgName   string                    `sh:"base" db:"basepkg_name"` | ||||||
|  | 	Name          string                    `sh:"name,required" db:"name"` | ||||||
|  | 	Version       string                    `sh:"version,required" db:"version"` | ||||||
|  | 	Release       int                       `sh:"release,required" db:"release"` | ||||||
|  | 	Epoch         uint                      `sh:"epoch" db:"epoch"` | ||||||
|  | 	Summary       JSON[map[string]string]   `db:"summary"` | ||||||
|  | 	Description   JSON[map[string]string]   `db:"description"` | ||||||
|  | 	Group         JSON[map[string]string]   `db:"group_name"` | ||||||
|  | 	Homepage      JSON[map[string]string]   `db:"homepage"` | ||||||
|  | 	Maintainer    JSON[map[string]string]   `db:"maintainer"` | ||||||
|  | 	Architectures JSON[[]string]            `sh:"architectures" db:"architectures"` | ||||||
|  | 	Licenses      JSON[[]string]            `sh:"license" db:"licenses"` | ||||||
|  | 	Provides      JSON[[]string]            `sh:"provides" db:"provides"` | ||||||
|  | 	Conflicts     JSON[[]string]            `sh:"conflicts" db:"conflicts"` | ||||||
|  | 	Replaces      JSON[[]string]            `sh:"replaces" db:"replaces"` | ||||||
|  | 	Depends       JSON[map[string][]string] `db:"depends"` | ||||||
|  | 	BuildDepends  JSON[map[string][]string] `db:"builddepends"` | ||||||
|  | 	OptDepends    JSON[map[string][]string] `db:"optdepends"` | ||||||
|  | 	Repository    string                    `db:"repository"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type version struct { | ||||||
|  | 	Version int `db:"version"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type Config interface { | type Config interface { | ||||||
| @@ -45,7 +65,7 @@ type Config interface { | |||||||
| } | } | ||||||
|  |  | ||||||
| type Database struct { | type Database struct { | ||||||
| 	engine *xorm.Engine | 	conn   *sqlx.DB | ||||||
| 	config Config | 	config Config | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -55,115 +75,181 @@ func New(config Config) *Database { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) Connect() error { | func (d *Database) Init(ctx context.Context) error { | ||||||
| 	dsn := d.config.GetPaths().DBPath | 	err := d.Connect(ctx) | ||||||
| 	 |  | ||||||
| 	// Проверяем директорию для БД |  | ||||||
| 	dbDir := filepath.Dir(dsn) |  | ||||||
| 	if _, err := os.Stat(dbDir); err != nil { |  | ||||||
| 		if os.IsNotExist(err) { |  | ||||||
| 			// Директория не существует - пытаемся создать |  | ||||||
| 			if mkErr := os.MkdirAll(dbDir, 0775); mkErr != nil { |  | ||||||
| 				// Не смогли создать - вернём ошибку, пользователь должен использовать alr fix |  | ||||||
| 				return fmt.Errorf("cache directory does not exist, please run 'alr fix' to create it: %w", mkErr) |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			return fmt.Errorf("failed to check database directory: %w", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	engine, err := xorm.NewEngine("sqlite", dsn) |  | ||||||
| 	// engine.SetLogLevel(log.LOG_DEBUG) |  | ||||||
| 	// engine.ShowSQL(true) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	d.engine = engine | 	return d.initDB(ctx) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Database) Connect(ctx context.Context) error { | ||||||
|  | 	dsn := d.config.GetPaths().DBPath | ||||||
|  | 	db, err := sqlx.Open("sqlite", dsn) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	d.conn = db | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) Init(ctx context.Context) error { | func (d *Database) GetConn() *sqlx.DB { | ||||||
| 	if err := d.Connect(); err != nil { | 	return d.conn | ||||||
| 		return err | } | ||||||
| 	} |  | ||||||
| 	if err := d.engine.Sync2(new(alrsh.Package), new(Version)); err != nil { | func (d *Database) initDB(ctx context.Context) error { | ||||||
|  | 	d.conn = d.conn.Unsafe() | ||||||
|  | 	conn := d.conn | ||||||
|  | 	_, err := conn.ExecContext(ctx, ` | ||||||
|  | 		CREATE TABLE IF NOT EXISTS pkgs ( | ||||||
|  | 			basepkg_name  TEXT NOT NULL, | ||||||
|  | 			name          TEXT NOT NULL, | ||||||
|  | 			repository    TEXT NOT NULL, | ||||||
|  | 			version       TEXT NOT NULL, | ||||||
|  | 			release       INT  NOT NULL, | ||||||
|  | 			epoch         INT, | ||||||
|  | 			summary       TEXT CHECK(summary = 'null' OR (JSON_VALID(summary) AND JSON_TYPE(summary) = 'object')), | ||||||
|  | 			description   TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')), | ||||||
|  | 			group_name    TEXT CHECK(group_name = 'null' OR (JSON_VALID(group_name) AND JSON_TYPE(group_name) = 'object')), | ||||||
|  | 			homepage      TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')), | ||||||
|  | 			maintainer    TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')), | ||||||
|  | 			architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')), | ||||||
|  | 			licenses      TEXT CHECK(licenses = 'null' OR (JSON_VALID(licenses) AND JSON_TYPE(licenses) = 'array')), | ||||||
|  | 			provides      TEXT CHECK(provides = 'null' OR (JSON_VALID(provides) AND JSON_TYPE(provides) = 'array')), | ||||||
|  | 			conflicts     TEXT CHECK(conflicts = 'null' OR (JSON_VALID(conflicts) AND JSON_TYPE(conflicts) = 'array')), | ||||||
|  | 			replaces      TEXT CHECK(replaces = 'null' OR (JSON_VALID(replaces) AND JSON_TYPE(replaces) = 'array')), | ||||||
|  | 			depends       TEXT CHECK(depends = 'null' OR (JSON_VALID(depends) AND JSON_TYPE(depends) = 'object')), | ||||||
|  | 			builddepends  TEXT CHECK(builddepends = 'null' OR (JSON_VALID(builddepends) AND JSON_TYPE(builddepends) = 'object')), | ||||||
|  | 			optdepends    TEXT CHECK(optdepends = 'null' OR (JSON_VALID(optdepends) AND JSON_TYPE(optdepends) = 'object')), | ||||||
|  | 			UNIQUE(name, repository) | ||||||
|  | 		); | ||||||
|  |  | ||||||
|  | 		CREATE TABLE IF NOT EXISTS alr_db_version ( | ||||||
|  | 			version INT NOT NULL | ||||||
|  | 		); | ||||||
|  | 	`) | ||||||
|  | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ver, ok := d.GetVersion(ctx) | 	ver, ok := d.GetVersion(ctx) | ||||||
| 	if ok && ver != CurrentVersion { | 	if ok && ver != CurrentVersion { | ||||||
| 		slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion) | 		slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion) | ||||||
| 		if err := d.reset(); err != nil { | 		err = d.reset(ctx) | ||||||
|  | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		return d.Init(ctx) | 		return d.initDB(ctx) | ||||||
| 	} else if !ok { | 	} else if !ok { | ||||||
| 		slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working.")) | 		slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion) | ||||||
| 		return d.addVersion(CurrentVersion) | 		return d.addVersion(ctx, CurrentVersion) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) GetVersion(ctx context.Context) (int, bool) { | func (d *Database) GetVersion(ctx context.Context) (int, bool) { | ||||||
| 	var v Version | 	var ver version | ||||||
| 	has, err := d.engine.Get(&v) | 	err := d.conn.GetContext(ctx, &ver, "SELECT * FROM alr_db_version LIMIT 1;") | ||||||
| 	if err != nil || !has { | 	if err != nil { | ||||||
| 		return 0, false | 		return 0, false | ||||||
| 	} | 	} | ||||||
| 	return v.Version, true | 	return ver.Version, true | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) addVersion(ver int) error { | func (d *Database) addVersion(ctx context.Context, ver int) error { | ||||||
| 	_, err := d.engine.Insert(&Version{Version: ver}) | 	_, err := d.conn.ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) reset() error { | func (d *Database) reset(ctx context.Context) error { | ||||||
| 	return d.engine.DropTables(new(alrsh.Package), new(Version)) | 	_, err := d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;") | ||||||
| } |  | ||||||
|  |  | ||||||
| func (d *Database) InsertPackage(ctx context.Context, pkg alrsh.Package) error { |  | ||||||
| 	session := d.engine.Context(ctx) |  | ||||||
|  |  | ||||||
| 	affected, err := session.Where("name = ? AND repository = ?", pkg.Name, pkg.Repository).Update(&pkg) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	_, err = d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS alr_db_version;") | ||||||
| 	if affected == 0 { |  | ||||||
| 		_, err = session.Insert(&pkg) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (d *Database) GetPkgs(_ context.Context, where string, args ...any) ([]alrsh.Package, error) { |  | ||||||
| 	var pkgs []alrsh.Package |  | ||||||
| 	err := d.engine.Where(where, args...).Find(&pkgs) |  | ||||||
| 	return pkgs, err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (d *Database) GetPkg(where string, args ...any) (*alrsh.Package, error) { |  | ||||||
| 	var pkg alrsh.Package |  | ||||||
| 	has, err := d.engine.Where(where, args...).Get(&pkg) |  | ||||||
| 	if err != nil || !has { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return &pkg, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (d *Database) DeletePkgs(_ context.Context, where string, args ...any) error { |  | ||||||
| 	_, err := d.engine.Where(where, args...).Delete(&alrsh.Package{}) |  | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) IsEmpty() bool { | func (d *Database) GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) { | ||||||
| 	count, err := d.engine.Count(new(alrsh.Package)) | 	stream, err := d.conn.QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...) | ||||||
| 	return err != nil || count == 0 | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return stream, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Database) GetPkg(ctx context.Context, where string, args ...any) (*Package, error) { | ||||||
|  | 	out := &Package{} | ||||||
|  | 	err := d.conn.GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...) | ||||||
|  | 	return out, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Database) DeletePkgs(ctx context.Context, where string, args ...any) error { | ||||||
|  | 	_, err := d.conn.ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Database) IsEmpty(ctx context.Context) bool { | ||||||
|  | 	var count int | ||||||
|  | 	err := d.conn.GetContext(ctx, &count, "SELECT count(1) FROM pkgs;") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return count == 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Database) InsertPackage(ctx context.Context, pkg Package) error { | ||||||
|  | 	_, err := d.conn.NamedExecContext(ctx, ` | ||||||
|  | 		INSERT OR REPLACE INTO pkgs ( | ||||||
|  | 			basepkg_name, | ||||||
|  | 			name, | ||||||
|  | 			repository, | ||||||
|  | 			version, | ||||||
|  | 			release, | ||||||
|  | 			epoch, | ||||||
|  | 			summary, | ||||||
|  | 			description, | ||||||
|  | 			group_name, | ||||||
|  | 			homepage, | ||||||
|  | 			maintainer, | ||||||
|  | 			architectures, | ||||||
|  | 			licenses, | ||||||
|  | 			provides, | ||||||
|  | 			conflicts, | ||||||
|  | 			replaces, | ||||||
|  | 			depends, | ||||||
|  | 			builddepends, | ||||||
|  | 			optdepends | ||||||
|  | 		) VALUES ( | ||||||
|  | 		 	:basepkg_name, | ||||||
|  | 			:name, | ||||||
|  | 			:repository, | ||||||
|  | 			:version, | ||||||
|  | 			:release, | ||||||
|  | 			:epoch, | ||||||
|  | 			:summary, | ||||||
|  | 			:description, | ||||||
|  | 			:group_name, | ||||||
|  | 			:homepage, | ||||||
|  | 			:maintainer, | ||||||
|  | 			:architectures, | ||||||
|  | 			:licenses, | ||||||
|  | 			:provides, | ||||||
|  | 			:conflicts, | ||||||
|  | 			:replaces, | ||||||
|  | 			:depends, | ||||||
|  | 			:builddepends, | ||||||
|  | 			:optdepends | ||||||
|  | 		); | ||||||
|  | 	`, pkg) | ||||||
|  | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) Close() error { | func (d *Database) Close() error { | ||||||
| 	return d.engine.Close() | 	if d.conn != nil { | ||||||
|  | 		return d.conn.Close() | ||||||
|  | 	} else { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,11 +25,10 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/jmoiron/sqlx" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type TestALRConfig struct{} | type TestALRConfig struct{} | ||||||
| @@ -46,38 +45,35 @@ func prepareDb() *db.Database { | |||||||
| 	return database | 	return database | ||||||
| } | } | ||||||
|  |  | ||||||
| var testPkg = alrsh.Package{ | var testPkg = db.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) { | ||||||
| @@ -103,16 +99,15 @@ func TestInsertPackage(t *testing.T) { | |||||||
| 		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(database.GetConn(), &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if 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) { | ||||||
| @@ -135,12 +130,18 @@ func TestGetPkgs(t *testing.T) { | |||||||
| 		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 := database.GetPkgs(ctx, "name LIKE 'x%'") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, 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) | ||||||
| 		} | 		} | ||||||
| @@ -167,7 +168,7 @@ func TestGetPkg(t *testing.T) { | |||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pkg, err := database.GetPkg("name LIKE 'x%'") | 	pkg, err := database.GetPkg(ctx, "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) | ||||||
| 	} | 	} | ||||||
| @@ -205,6 +206,16 @@ func TestDeletePkgs(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	var dbPkg db.Package | ||||||
|  | 	err = database.GetConn().Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("Expected no error, got %s", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if dbPkg.Name != "x2" { | ||||||
|  | 		t.Errorf("Expected x2 package, got %s", dbPkg.Name) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestJsonArrayContains(t *testing.T) { | func TestJsonArrayContains(t *testing.T) { | ||||||
| @@ -216,7 +227,7 @@ func TestJsonArrayContains(t *testing.T) { | |||||||
| 	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 := database.InsertPackage(ctx, x1) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -228,24 +239,13 @@ func TestJsonArrayContains(t *testing.T) { | |||||||
| 		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 = database.GetConn().Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if 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'") |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										80
									
								
								internal/db/json.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								internal/db/json.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package db | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"database/sql" | ||||||
|  | 	"database/sql/driver" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // JSON represents a JSON value in the database | ||||||
|  | type JSON[T any] struct { | ||||||
|  | 	Val T | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewJSON creates a new database JSON value | ||||||
|  | func NewJSON[T any](v T) JSON[T] { | ||||||
|  | 	return JSON[T]{Val: v} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *JSON[T]) Scan(val any) error { | ||||||
|  | 	if val == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch val := val.(type) { | ||||||
|  | 	case string: | ||||||
|  | 		err := json.Unmarshal([]byte(val), &s.Val) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	case sql.NullString: | ||||||
|  | 		if val.Valid { | ||||||
|  | 			err := json.Unmarshal([]byte(val.String), &s.Val) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
|  | 		return errors.New("sqlite json types must be strings") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s JSON[T]) Value() (driver.Value, error) { | ||||||
|  | 	data, err := json.Marshal(s.Val) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return string(data), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s JSON[T]) MarshalYAML() (any, error) { | ||||||
|  | 	return s.Val, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s JSON[T]) String() string { | ||||||
|  | 	return fmt.Sprint(s.Val) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s JSON[T]) GoString() string { | ||||||
|  | 	return fmt.Sprintf("%#v", s.Val) | ||||||
|  | } | ||||||
| @@ -55,7 +55,7 @@ var ( | |||||||
| 
 | 
 | ||||||
| // Массив доступных загрузчиков в порядке их проверки | // Массив доступных загрузчиков в порядке их проверки | ||||||
| var Downloaders = []Downloader{ | var Downloaders = []Downloader{ | ||||||
| 	&GitDownloader{}, | 	GitDownloader{}, | ||||||
| 	TorrentDownloader{}, | 	TorrentDownloader{}, | ||||||
| 	FileDownloader{}, | 	FileDownloader{}, | ||||||
| } | } | ||||||
| @@ -172,10 +172,15 @@ func Download(ctx context.Context, opts Options) (err error) { | |||||||
| 				"downloader", d.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 | ||||||
| 			} | 			} | ||||||
| @@ -221,10 +226,15 @@ func Download(ctx context.Context, opts Options) (err error) { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	newOpts := opts | 	t, name, err := d.Download(ctx, 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 +290,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 +306,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 +327,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) | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -32,8 +32,8 @@ import ( | |||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type TestALRConfig struct{} | type TestALRConfig struct{} | ||||||
| @@ -155,7 +155,7 @@ func TestDownloadFileWithCache(t *testing.T) { | |||||||
| 				CacheDisabled: false, | 				CacheDisabled: false, | ||||||
| 				URL:           server.URL + "/file", | 				URL:           server.URL + "/file", | ||||||
| 				Destination:   tmpdir, | 				Destination:   tmpdir, | ||||||
| 				DlCache:       dlcache.New(cfg.GetPaths().CacheDir), | 				DlCache:       dlcache.New(cfg), | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			outputFile := path.Join(tmpdir, "file") | 			outputFile := path.Join(tmpdir, "file") | ||||||
| @@ -108,7 +108,7 @@ 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) | ||||||
| @@ -154,7 +154,7 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string, | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Проверка необходимости постобработки | 	// Проверка необходимости постобработки | ||||||
| 	if postprocDisabled { | 	if opts.PostprocDisabled { | ||||||
| 		return TypeFile, name, nil | 		return TypeFile, name, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -22,7 +22,6 @@ package dl | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" |  | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"path" | 	"path" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| @@ -49,7 +48,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(ctx context.Context, 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 +60,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") | ||||||
| 
 | 
 | ||||||
| @@ -125,11 +121,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 +133,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 +141,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 +169,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 | ||||||
| } | } | ||||||
| @@ -71,17 +71,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 { | ||||||
| @@ -32,15 +32,19 @@ type Config interface { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type DownloadCache struct { | type DownloadCache struct { | ||||||
| 	cacheDir string | 	cfg Config | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func New(cacheDir string) *DownloadCache { | func New(cfg Config) *DownloadCache { | ||||||
| 	return &DownloadCache{cacheDir} | 	return &DownloadCache{ | ||||||
|  | 		cfg, | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dc *DownloadCache) BasePath(ctx context.Context) string { | func (dc *DownloadCache) BasePath(ctx context.Context) string { | ||||||
| 	return filepath.Join(dc.cacheDir, "dl") | 	return filepath.Join( | ||||||
|  | 		dc.cfg.GetPaths().CacheDir, "dl", | ||||||
|  | 	) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // New creates a new directory with the given ID in the cache. | // New creates a new directory with the given ID in the cache. | ||||||
| @@ -61,8 +65,7 @@ func (dc *DownloadCache) New(ctx context.Context, id string) (string, error) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Создаем директорию с правильными правами (различается для prod и тестов) | 	err = os.MkdirAll(itemPath, 0o755) | ||||||
| 	err = createDir(itemPath, 0o2775) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| @@ -29,7 +29,7 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type TestALRConfig struct { | type TestALRConfig struct { | ||||||
| @@ -45,7 +45,7 @@ func (c *TestALRConfig) GetPaths() *config.Paths { | |||||||
| func prepare(t *testing.T) *TestALRConfig { | func prepare(t *testing.T) *TestALRConfig { | ||||||
| 	t.Helper() | 	t.Helper() | ||||||
| 
 | 
 | ||||||
| 	dir, err := os.MkdirTemp("", "alr-dlcache-test.*") | 	dir, err := os.MkdirTemp("/tmp", "alr-dlcache-test.*") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
| 	} | 	} | ||||||
| @@ -57,14 +57,14 @@ func prepare(t *testing.T) *TestALRConfig { | |||||||
| 
 | 
 | ||||||
| func cleanup(t *testing.T, cfg *TestALRConfig) { | func cleanup(t *testing.T, cfg *TestALRConfig) { | ||||||
| 	t.Helper() | 	t.Helper() | ||||||
| 	os.RemoveAll(cfg.CacheDir) | 	os.Remove(cfg.CacheDir) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNew(t *testing.T) { | func TestNew(t *testing.T) { | ||||||
| 	cfg := prepare(t) | 	cfg := prepare(t) | ||||||
| 	defer cleanup(t, cfg) | 	defer cleanup(t, cfg) | ||||||
| 
 | 
 | ||||||
| 	dc := dlcache.New(cfg.GetPaths().CacheDir) | 	dc := dlcache.New(cfg) | ||||||
| 
 | 
 | ||||||
| 	ctx := context.Background() | 	ctx := context.Background() | ||||||
| 
 | 
 | ||||||
| @@ -82,12 +82,6 @@ func TestNew(t *testing.T) { | |||||||
| 	fi, err := os.Stat(dir) | 	fi, err := os.Stat(dir) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("stat: expected no error, got %s", err) | 		t.Errorf("stat: expected no error, got %s", err) | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if fi == nil { |  | ||||||
| 		t.Errorf("Expected file info to not be nil") |  | ||||||
| 		return |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !fi.IsDir() { | 	if !fi.IsDir() { | ||||||
| @@ -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) |  | ||||||
| } |  | ||||||
| @@ -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}} |  | ||||||
| } |  | ||||||
| @@ -65,8 +65,6 @@ func (a *HCLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{} | |||||||
| 	var chLogLevel chLog.Level | 	var chLogLevel chLog.Level | ||||||
| 	if msg == "plugin process exited" || | 	if msg == "plugin process exited" || | ||||||
| 		strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") || | 		strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") || | ||||||
| 		strings.HasPrefix(msg, "[WARN] error closing client during Kill") || |  | ||||||
| 		strings.HasPrefix(msg, "[WARN] plugin failed to exit gracefully") || |  | ||||||
| 		strings.HasPrefix(msg, "[DEBUG] plugin") { | 		strings.HasPrefix(msg, "[DEBUG] plugin") { | ||||||
| 		chLogLevel = chLog.DebugLevel | 		chLogLevel = chLog.DebugLevel | ||||||
| 	} else { | 	} else { | ||||||
|   | |||||||
| @@ -21,14 +21,15 @@ package overrides | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"reflect" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strconv" |  | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"golang.org/x/exp/slices" | 	"golang.org/x/exp/slices" | ||||||
| 	"golang.org/x/text/language" | 	"golang.org/x/text/language" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -149,6 +150,65 @@ func (o *Opts) WithLanguageTags(langs []string) *Opts { | |||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ResolvedPackage is a ALR package after its overrides | ||||||
|  | // have been resolved | ||||||
|  | type ResolvedPackage struct { | ||||||
|  | 	Name          string   `sh:"name"` | ||||||
|  | 	Version       string   `sh:"version"` | ||||||
|  | 	Release       int      `sh:"release"` | ||||||
|  | 	Epoch         uint     `sh:"epoch"` | ||||||
|  | 	Group         string   `db:"group_name"` | ||||||
|  | 	Summary       string   `db:"summary"` | ||||||
|  | 	Description   string   `db:"description"` | ||||||
|  | 	Homepage      string   `db:"homepage"` | ||||||
|  | 	Maintainer    string   `db:"maintainer"` | ||||||
|  | 	Architectures []string `sh:"architectures"` | ||||||
|  | 	Licenses      []string `sh:"license"` | ||||||
|  | 	Provides      []string `sh:"provides"` | ||||||
|  | 	Conflicts     []string `sh:"conflicts"` | ||||||
|  | 	Replaces      []string `sh:"replaces"` | ||||||
|  | 	Depends       []string `sh:"deps"` | ||||||
|  | 	BuildDepends  []string `sh:"build_deps"` | ||||||
|  | 	OptDepends    []string `sh:"opt_deps"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ResolvePackage(pkg *db.Package, overrides []string) *ResolvedPackage { | ||||||
|  | 	out := &ResolvedPackage{} | ||||||
|  | 	outVal := reflect.ValueOf(out).Elem() | ||||||
|  | 	pkgVal := reflect.ValueOf(pkg).Elem() | ||||||
|  |  | ||||||
|  | 	for i := 0; i < outVal.NumField(); i++ { | ||||||
|  | 		fieldVal := outVal.Field(i) | ||||||
|  | 		fieldType := fieldVal.Type() | ||||||
|  | 		pkgFieldVal := pkgVal.FieldByName(outVal.Type().Field(i).Name) | ||||||
|  | 		pkgFieldType := pkgFieldVal.Type() | ||||||
|  |  | ||||||
|  | 		if strings.HasPrefix(pkgFieldType.String(), "db.JSON") { | ||||||
|  | 			pkgFieldVal = pkgFieldVal.FieldByName("Val") | ||||||
|  | 			pkgFieldType = pkgFieldVal.Type() | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if pkgFieldType.AssignableTo(fieldType) { | ||||||
|  | 			fieldVal.Set(pkgFieldVal) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if pkgFieldVal.Kind() == reflect.Map && pkgFieldType.Elem().AssignableTo(fieldType) { | ||||||
|  | 			for _, override := range overrides { | ||||||
|  | 				overrideVal := pkgFieldVal.MapIndex(reflect.ValueOf(override)) | ||||||
|  | 				if !overrideVal.IsValid() { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				fieldVal.Set(overrideVal) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  |  | ||||||
| func parseLangs(langs []string, tags []language.Tag) ([]string, error) { | func parseLangs(langs []string, tags []language.Tag) ([]string, error) { | ||||||
| 	out := make([]string, len(tags)+len(langs)) | 	out := make([]string, len(tags)+len(langs)) | ||||||
| 	for i, tag := range tags { | 	for i, tag := range tags { | ||||||
| @@ -183,18 +243,3 @@ func ReleasePlatformSpecific(release int, info *distro.OSRelease) string { | |||||||
|  |  | ||||||
| 	return fmt.Sprintf("%d", release) | 	return fmt.Sprintf("%d", release) | ||||||
| } | } | ||||||
|  |  | ||||||
| func ParseReleasePlatformSpecific(s string, info *distro.OSRelease) (int, error) { |  | ||||||
| 	if info.ID == "altlinux" { |  | ||||||
| 		if strings.HasPrefix(s, "alt") { |  | ||||||
| 			return strconv.Atoi(s[3:]) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if info.ID == "fedora" || slices.Contains(info.Like, "fedora") { |  | ||||||
| 		parts := strings.SplitN(s, ".", 2) |  | ||||||
| 		return strconv.Atoi(parts[0]) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return strconv.Atoi(s) |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -233,8 +233,5 @@ func TestReleasePlatformSpecific(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 	} { | 	} { | ||||||
| 		assert.Equal(t, tc.expected, overrides.ReleasePlatformSpecific(1, tc.info)) | 		assert.Equal(t, tc.expected, overrides.ReleasePlatformSpecific(1, tc.info)) | ||||||
| 		release, err := overrides.ParseReleasePlatformSpecific(tc.expected, tc.info) |  | ||||||
| 		assert.NoError(t, err) |  | ||||||
| 		assert.Equal(t, 1, release) |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,534 +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 repos |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"log/slog" |  | ||||||
| 	"net/url" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"github.com/go-git/go-billy/v5" |  | ||||||
| 	"github.com/go-git/go-git/v5" |  | ||||||
| 	gitConfig "github.com/go-git/go-git/v5/config" |  | ||||||
| 	"github.com/go-git/go-git/v5/plumbing" |  | ||||||
| 	"github.com/leonelquinteros/gotext" |  | ||||||
| 	"github.com/pelletier/go-toml/v2" |  | ||||||
| 	"go.elara.ws/vercmp" |  | ||||||
| 	"mvdan.cc/sh/v3/expand" |  | ||||||
| 	"mvdan.cc/sh/v3/interp" |  | ||||||
| 	"mvdan.cc/sh/v3/syntax" |  | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type actionType uint8 |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	actionDelete actionType = iota |  | ||||||
| 	actionUpdate |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type action struct { |  | ||||||
| 	Type actionType |  | ||||||
| 	File string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Pull pulls the provided repositories. If a repo doesn't exist, it will be cloned |  | ||||||
| // and its packages will be written to the DB. If it does exist, it will be pulled. |  | ||||||
| // In this case, only changed packages will be processed if possible. |  | ||||||
| // If repos is set to nil, the repos in the ALR config will be used. |  | ||||||
| func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { |  | ||||||
| 	if repos == nil { |  | ||||||
| 		repos = rs.cfg.Repos() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, repo := range repos { |  | ||||||
| 		err := rs.pullRepo(ctx, &repo, false) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (rs *Repos) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) error { |  | ||||||
| 	err := rs.pullRepo(ctx, repo, true) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (rs *Repos) pullRepo(ctx context.Context, repo *types.Repo, updateRepoFromToml bool) error { |  | ||||||
| 	urls := []string{repo.URL} |  | ||||||
| 	urls = append(urls, repo.Mirrors...) |  | ||||||
|  |  | ||||||
| 	var lastErr error |  | ||||||
|  |  | ||||||
| 	for i, repoURL := range urls { |  | ||||||
| 		if i > 0 { |  | ||||||
| 			slog.Info(gotext.Get("Trying mirror"), "repo", repo.Name, "mirror", repoURL) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		err := rs.pullRepoFromURL(ctx, repoURL, repo, updateRepoFromToml) |  | ||||||
| 		if err != nil { |  | ||||||
| 			lastErr = err |  | ||||||
| 			slog.Warn(gotext.Get("Failed to pull from URL"), "repo", repo.Name, "url", repoURL, "error", err) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Success |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return fmt.Errorf("failed to pull repository %s from any URL: %w", repo.Name, lastErr) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func readGitRepo(repoDir, repoUrl string) (*git.Repository, bool, error) { |  | ||||||
| 	gitDir := filepath.Join(repoDir, ".git") |  | ||||||
| 	if fi, err := os.Stat(gitDir); err == nil && fi.IsDir() { |  | ||||||
| 		r, err := git.PlainOpen(repoDir) |  | ||||||
| 		if err == nil { |  | ||||||
| 			err = updateRemoteURL(r, repoUrl) |  | ||||||
| 			if err == nil { |  | ||||||
| 				_, err := r.Head() |  | ||||||
| 				if err == nil { |  | ||||||
| 					return r, false, nil |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				if errors.Is(err, plumbing.ErrReferenceNotFound) { |  | ||||||
| 					return r, true, nil |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				slog.Debug("error getting HEAD, reinitializing...", "err", err) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		slog.Debug("error while reading repo, reinitializing...", "err", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err := os.RemoveAll(repoDir); err != nil { |  | ||||||
| 		return nil, false, fmt.Errorf("failed to remove repo directory: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err := os.MkdirAll(repoDir, 0o755); err != nil { |  | ||||||
| 		return nil, false, fmt.Errorf("failed to create repo directory: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	r, err := git.PlainInit(repoDir, false) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, false, fmt.Errorf("failed to initialize git repo: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	_, err = r.CreateRemote(&gitConfig.RemoteConfig{ |  | ||||||
| 		Name: git.DefaultRemoteName, |  | ||||||
| 		URLs: []string{repoUrl}, |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, false, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return r, true, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo *types.Repo, update bool) error { |  | ||||||
| 	repoURL, err := url.Parse(rawRepoUrl) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("invalid URL %s: %w", rawRepoUrl, err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	slog.Info(gotext.Get("Pulling repository"), "name", repo.Name) |  | ||||||
| 	repoDir := filepath.Join(rs.cfg.GetPaths().RepoDir, repo.Name) |  | ||||||
|  |  | ||||||
| 	var repoFS billy.Filesystem |  | ||||||
|  |  | ||||||
| 	r, freshGit, err := readGitRepo(repoDir, repoURL.String()) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("failed to open repo") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	err = r.FetchContext(ctx, &git.FetchOptions{ |  | ||||||
| 		Progress: os.Stderr, |  | ||||||
| 		Force:    true, |  | ||||||
| 	}) |  | ||||||
| 	if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var old *plumbing.Reference |  | ||||||
|  |  | ||||||
| 	w, err := r.Worktree() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	revHash, err := resolveHash(r, repo.Ref) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("error resolving hash: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if !freshGit { |  | ||||||
| 		old, err = r.Head() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if old.Hash() == *revHash { |  | ||||||
| 			slog.Info(gotext.Get("Repository up to date"), "name", repo.Name) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	err = w.Checkout(&git.CheckoutOptions{ |  | ||||||
| 		Hash:  plumbing.NewHash(revHash.String()), |  | ||||||
| 		Force: true, |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	repoFS = w.Filesystem |  | ||||||
|  |  | ||||||
| 	new, err := r.Head() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// If the DB was not present at startup, that means it's |  | ||||||
| 	// empty. In this case, we need to update the DB fully |  | ||||||
| 	// rather than just incrementally. |  | ||||||
| 	if rs.db.IsEmpty() || freshGit { |  | ||||||
| 		err = rs.processRepoFull(ctx, *repo, repoDir) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		err = rs.processRepoChanges(ctx, *repo, r, w, old, new) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fl, err := repoFS.Open("alr-repo.toml") |  | ||||||
| 	if err != nil { |  | ||||||
| 		slog.Warn(gotext.Get("Git repository does not appear to be a valid ALR repo"), "repo", repo.Name) |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var repoCfg types.RepoConfig |  | ||||||
| 	err = toml.NewDecoder(fl).Decode(&repoCfg) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	fl.Close() |  | ||||||
|  |  | ||||||
| 	// If the version doesn't have a "v" prefix, it's not a standard version. |  | ||||||
| 	// It may be "unknown" or a git version, but either way, there's no way |  | ||||||
| 	// to compare it to the repo version, so only compare versions with the "v". |  | ||||||
| 	if strings.HasPrefix(config.Version, "v") { |  | ||||||
| 		if vercmp.Compare(config.Version, repoCfg.Repo.MinVersion) == -1 { |  | ||||||
| 			slog.Warn(gotext.Get("ALR repo's minimum ALR version is greater than the current version. Try updating ALR if something doesn't work."), "repo", repo.Name) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if update { |  | ||||||
| 		if repoCfg.Repo.URL != "" { |  | ||||||
| 			repo.URL = repoCfg.Repo.URL |  | ||||||
| 		} |  | ||||||
| 		if repoCfg.Repo.Ref != "" { |  | ||||||
| 			repo.Ref = repoCfg.Repo.Ref |  | ||||||
| 		} |  | ||||||
| 		if len(repoCfg.Repo.Mirrors) > 0 { |  | ||||||
| 			repo.Mirrors = repoCfg.Repo.Mirrors |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func updateRemoteURL(r *git.Repository, newURL string) error { |  | ||||||
| 	cfg, err := r.Config() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	remote, ok := cfg.Remotes[git.DefaultRemoteName] |  | ||||||
| 	if !ok || len(remote.URLs) == 0 { |  | ||||||
| 		return fmt.Errorf("no remote '%s' found", git.DefaultRemoteName) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	currentURL := remote.URLs[0] |  | ||||||
| 	if currentURL == newURL { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	slog.Debug("Updating remote URL", "old", currentURL, "new", newURL) |  | ||||||
|  |  | ||||||
| 	err = r.DeleteRemote(git.DefaultRemoteName) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("failed to delete old remote: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	_, err = r.CreateRemote(&gitConfig.RemoteConfig{ |  | ||||||
| 		Name: git.DefaultRemoteName, |  | ||||||
| 		URLs: []string{newURL}, |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("failed to create new remote: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (rs *Repos) updatePkg(ctx context.Context, repo types.Repo, runner *interp.Runner, scriptFl io.ReadCloser) error { |  | ||||||
| 	parser := syntax.NewParser() |  | ||||||
|  |  | ||||||
| 	pkgs, err := parseScript(ctx, repo, parser, runner, scriptFl) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, pkg := range pkgs { |  | ||||||
| 		err = rs.db.InsertPackage(ctx, *pkg) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Runner, error) { |  | ||||||
| 	env := append(os.Environ(), "scriptdir="+scriptDir) |  | ||||||
| 	return interp.New( |  | ||||||
| 		interp.Env(expand.ListEnviron(env...)), |  | ||||||
| 		interp.ExecHandler(handlers.NopExec), |  | ||||||
| 		interp.ReadDirHandler2(handlers.RestrictedReadDir(repoDir)), |  | ||||||
| 		interp.StatHandler(handlers.RestrictedStat(repoDir)), |  | ||||||
| 		interp.OpenHandler(handlers.RestrictedOpen(repoDir)), |  | ||||||
| 		interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}), |  | ||||||
| 		// Use temp dir instead script dir because runner may be for deleted file |  | ||||||
| 		interp.Dir(os.TempDir()), |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference) error { |  | ||||||
| 	oldCommit, err := r.CommitObject(old.Hash()) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	newCommit, err := r.CommitObject(new.Hash()) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	patch, err := oldCommit.Patch(newCommit) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("error to create patch: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var actions []action |  | ||||||
| 	for _, fp := range patch.FilePatches() { |  | ||||||
| 		from, to := fp.Files() |  | ||||||
|  |  | ||||||
| 		var isValidPath bool |  | ||||||
| 		if from != nil { |  | ||||||
| 			isValidPath = isValidScriptPath(from.Path()) |  | ||||||
| 		} |  | ||||||
| 		if to != nil { |  | ||||||
| 			isValidPath = isValidPath || isValidScriptPath(to.Path()) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !isValidPath { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		switch { |  | ||||||
| 		case to == nil: |  | ||||||
| 			actions = append(actions, action{ |  | ||||||
| 				Type: actionDelete, |  | ||||||
| 				File: from.Path(), |  | ||||||
| 			}) |  | ||||||
| 		case from == nil: |  | ||||||
| 			actions = append(actions, action{ |  | ||||||
| 				Type: actionUpdate, |  | ||||||
| 				File: to.Path(), |  | ||||||
| 			}) |  | ||||||
| 		case from.Path() != to.Path(): |  | ||||||
| 			actions = append(actions, |  | ||||||
| 				action{ |  | ||||||
| 					Type: actionDelete, |  | ||||||
| 					File: from.Path(), |  | ||||||
| 				}, |  | ||||||
| 				action{ |  | ||||||
| 					Type: actionUpdate, |  | ||||||
| 					File: to.Path(), |  | ||||||
| 				}, |  | ||||||
| 			) |  | ||||||
| 		default: |  | ||||||
| 			slog.Debug("unexpected, but I'll try to do") |  | ||||||
| 			actions = append(actions, action{ |  | ||||||
| 				Type: actionUpdate, |  | ||||||
| 				File: to.Path(), |  | ||||||
| 			}) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	repoDir := w.Filesystem.Root() |  | ||||||
| 	parser := syntax.NewParser() |  | ||||||
|  |  | ||||||
| 	for _, action := range actions { |  | ||||||
| 		var scriptDir string |  | ||||||
| 		if filepath.Dir(action.File) == "." { |  | ||||||
| 			scriptDir = repoDir |  | ||||||
| 		} else { |  | ||||||
| 			scriptDir = filepath.Dir(filepath.Join(repoDir, action.File)) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		runner, err := rs.processRepoChangesRunner(repoDir, scriptDir) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("error creating process repo changes runner: %w", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		switch action.Type { |  | ||||||
| 		case actionDelete: |  | ||||||
| 			scriptFl, err := oldCommit.File(action.File) |  | ||||||
| 			if err != nil { |  | ||||||
| 				slog.Warn("Failed to get deleted file from old commit", "file", action.File, "error", err) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			r, err := scriptFl.Reader() |  | ||||||
| 			if err != nil { |  | ||||||
| 				slog.Warn("Failed to read deleted file", "file", action.File, "error", err) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			pkgs, err := parseScript(ctx, repo, parser, runner, r) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return fmt.Errorf("error parsing deleted script %s: %w", action.File, err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			for _, pkg := range pkgs { |  | ||||||
| 				err = rs.db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return fmt.Errorf("error deleting package %s: %w", pkg.Name, err) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 		case actionUpdate: |  | ||||||
| 			scriptFl, err := newCommit.File(action.File) |  | ||||||
| 			if err != nil { |  | ||||||
| 				slog.Warn("Failed to get updated file from new commit", "file", action.File, "error", err) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			r, err := scriptFl.Reader() |  | ||||||
| 			if err != nil { |  | ||||||
| 				slog.Warn("Failed to read updated file", "file", action.File, "error", err) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			err = rs.updatePkg(ctx, repo, runner, r) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return fmt.Errorf("error updating package from %s: %w", action.File, err) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func isValidScriptPath(path string) bool { |  | ||||||
| 	if filepath.Base(path) != "alr.sh" { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	dir := filepath.Dir(path) |  | ||||||
| 	return dir == "." || !strings.Contains(strings.TrimPrefix(dir, "./"), "/") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error { |  | ||||||
| 	rootScript := filepath.Join(repoDir, "alr.sh") |  | ||||||
| 	if fi, err := os.Stat(rootScript); err == nil && !fi.IsDir() { |  | ||||||
| 		slog.Debug("Found root alr.sh, processing single-script repository", "repo", repo.Name) |  | ||||||
|  |  | ||||||
| 		runner, err := rs.processRepoChangesRunner(repoDir, repoDir) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("error creating runner for root alr.sh: %w", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		scriptFl, err := os.Open(rootScript) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("error opening root alr.sh: %w", err) |  | ||||||
| 		} |  | ||||||
| 		defer scriptFl.Close() |  | ||||||
|  |  | ||||||
| 		err = rs.updatePkg(ctx, repo, runner, scriptFl) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("error processing root alr.sh: %w", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	glob := filepath.Join(repoDir, "*/alr.sh") |  | ||||||
| 	matches, err := filepath.Glob(glob) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("error globbing for alr.sh files: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(matches) == 0 { |  | ||||||
| 		slog.Warn("No alr.sh files found in repository", "repo", repo.Name) |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	slog.Debug("Found multiple alr.sh files, processing multi-package repository", |  | ||||||
| 		"repo", repo.Name, "count", len(matches)) |  | ||||||
|  |  | ||||||
| 	for _, match := range matches { |  | ||||||
| 		runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match)) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("error creating runner for %s: %w", match, err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		scriptFl, err := os.Open(match) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("error opening %s: %w", match, err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		err = rs.updatePkg(ctx, repo, runner, scriptFl) |  | ||||||
| 		scriptFl.Close() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("error processing %s: %w", match, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| @@ -1,112 +0,0 @@ | |||||||
| // ALR - Any Linux Repository |  | ||||||
| // Copyright (C) 2025 The ALR Authors |  | ||||||
| // |  | ||||||
| // This program is free software: you can redistribute it and/or modify |  | ||||||
| // it under the terms of the GNU General Public License as published by |  | ||||||
| // the Free Software Foundation, either version 3 of the License, or |  | ||||||
| // (at your option) any later version. |  | ||||||
| // |  | ||||||
| // This program is distributed in the hope that it will be useful, |  | ||||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| // GNU General Public License for more details. |  | ||||||
| // |  | ||||||
| // You should have received a copy of the GNU General Public License |  | ||||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
| package repos |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
|  |  | ||||||
| 	"github.com/go-git/go-git/v5" |  | ||||||
| 	"github.com/go-git/go-git/v5/plumbing" |  | ||||||
| 	"github.com/go-git/go-git/v5/plumbing/transport" |  | ||||||
| 	"github.com/go-git/go-git/v5/plumbing/transport/client" |  | ||||||
|  |  | ||||||
| 	"mvdan.cc/sh/v3/interp" |  | ||||||
| 	"mvdan.cc/sh/v3/syntax" |  | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func parseScript( |  | ||||||
| 	ctx context.Context, |  | ||||||
| 	repo types.Repo, |  | ||||||
| 	syntaxParser *syntax.Parser, |  | ||||||
| 	runner *interp.Runner, |  | ||||||
| 	r io.ReadCloser, |  | ||||||
| ) ([]*alrsh.Package, error) { |  | ||||||
| 	f, err := alrsh.ReadFromIOReader(r, "/tmp") |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	_, dbPkgs, err := f.ParseBuildVars(ctx, &distro.OSRelease{}, []string{}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	for _, pkg := range dbPkgs { |  | ||||||
| 		pkg.Repository = repo.Name |  | ||||||
| 	} |  | ||||||
| 	return dbPkgs, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func getHeadReference(r *git.Repository) (plumbing.ReferenceName, error) { |  | ||||||
| 	remote, err := r.Remote(git.DefaultRemoteName) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	endpoint, err := transport.NewEndpoint(remote.Config().URLs[0]) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	gitClient, err := client.NewClient(endpoint) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	session, err := gitClient.NewUploadPackSession(endpoint, nil) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	info, err := session.AdvertisedReferences() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	refs, err := info.AllReferences() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return refs["HEAD"].Target(), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func resolveHash(r *git.Repository, ref string) (*plumbing.Hash, error) { |  | ||||||
| 	var err error |  | ||||||
|  |  | ||||||
| 	if ref == "" { |  | ||||||
| 		reference, err := getHeadReference(r) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("failed to get head reference %w", err) |  | ||||||
| 		} |  | ||||||
| 		ref = reference.Short() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	hsh, err := r.ResolveRevision(git.DefaultRemoteName + "/" + plumbing.Revision(ref)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		hsh, err = r.ResolveRevision(plumbing.Revision(ref)) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return hsh, nil |  | ||||||
| } |  | ||||||
| @@ -22,7 +22,6 @@ package decoder | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" |  | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| @@ -53,7 +52,7 @@ type InvalidTypeError struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (ite InvalidTypeError) Error() string { | func (ite InvalidTypeError) Error() string { | ||||||
| 	return fmt.Sprintf("variable '%s' is of type %s, but %s is expected", ite.name, ite.vartype, ite.exptype) | 	return "variable '" + ite.name + "' is of type " + ite.vartype + ", but " + ite.exptype + " is expected" | ||||||
| } | } | ||||||
|  |  | ||||||
| // Decoder provides methods for decoding variable values | // Decoder provides methods for decoding variable values | ||||||
| @@ -74,99 +73,39 @@ func New(info *distro.OSRelease, runner *interp.Runner) *Decoder { | |||||||
| // DecodeVar decodes a variable to val using reflection. | // DecodeVar decodes a variable to val using reflection. | ||||||
| // Structs should use the "sh" struct tag. | // Structs should use the "sh" struct tag. | ||||||
| func (d *Decoder) DecodeVar(name string, val any) error { | func (d *Decoder) DecodeVar(name string, val any) error { | ||||||
| 	origType := reflect.TypeOf(val).Elem() | 	variable := d.getVar(name) | ||||||
| 	isOverridableField := strings.Contains(origType.String(), "OverridableField[") | 	if variable == nil { | ||||||
|  | 		return VarNotFoundError{name} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if !isOverridableField { | 	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ | ||||||
| 		variable := d.getVarNoOverrides(name) | 		WeaklyTypedInput: true, | ||||||
| 		if variable == nil { | 		DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) { | ||||||
| 			return VarNotFoundError{name} | 			if strings.Contains(to.Type().String(), "db.JSON") { | ||||||
| 		} | 				valType := to.FieldByName("Val").Type() | ||||||
|  | 				if !from.Type().AssignableTo(valType) { | ||||||
| 		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ | 					return nil, InvalidTypeError{name, from.Type().String(), valType.String()} | ||||||
| 			WeaklyTypedInput: true, |  | ||||||
| 			Result:           val, // передаем указатель на новое значение |  | ||||||
| 			TagName:          "sh", |  | ||||||
| 			DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) { |  | ||||||
| 				if from.Kind() == reflect.Slice && to.Kind() == reflect.String { |  | ||||||
| 					s, ok := from.Interface().([]string) |  | ||||||
| 					if ok && len(s) == 1 { |  | ||||||
| 						return s[0], nil |  | ||||||
| 					} |  | ||||||
| 				} | 				} | ||||||
| 				return from.Interface(), nil |  | ||||||
| 			}), |  | ||||||
| 		}) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		switch variable.Kind { | 				to.FieldByName("Val").Set(from) | ||||||
| 		case expand.Indexed: | 				return to, nil | ||||||
| 			return dec.Decode(variable.List) |  | ||||||
| 		case expand.Associative: |  | ||||||
| 			return dec.Decode(variable.Map) |  | ||||||
| 		default: |  | ||||||
| 			return dec.Decode(variable.Str) |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		vars := d.getVarsByPrefix(name) |  | ||||||
|  |  | ||||||
| 		if len(vars) == 0 { |  | ||||||
| 			return VarNotFoundError{name} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		reflectVal := reflect.ValueOf(val) |  | ||||||
| 		overridableVal := reflect.ValueOf(val).Elem() |  | ||||||
|  |  | ||||||
| 		dataField := overridableVal.FieldByName("data") |  | ||||||
| 		if !dataField.IsValid() { |  | ||||||
| 			return fmt.Errorf("data field not found in OverridableField") |  | ||||||
| 		} |  | ||||||
| 		mapType := dataField.Type() // map[string]T |  | ||||||
| 		elemType := mapType.Elem()  // T |  | ||||||
|  |  | ||||||
| 		var overridablePtr reflect.Value |  | ||||||
| 		if reflectVal.Kind() == reflect.Ptr { |  | ||||||
| 			overridablePtr = reflectVal |  | ||||||
| 		} else { |  | ||||||
| 			if !reflectVal.CanAddr() { |  | ||||||
| 				return fmt.Errorf("OverridableField value is not addressable") |  | ||||||
| 			} | 			} | ||||||
| 			overridablePtr = reflectVal.Addr() | 			return from.Interface(), nil | ||||||
| 		} | 		}), | ||||||
|  | 		Result:  val, | ||||||
|  | 		TagName: "sh", | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 		setValue := overridablePtr.MethodByName("Set") | 	switch variable.Kind { | ||||||
| 		if !setValue.IsValid() { | 	case expand.Indexed: | ||||||
| 			return fmt.Errorf("method Set not found on OverridableField") | 		return dec.Decode(variable.List) | ||||||
| 		} | 	case expand.Associative: | ||||||
|  | 		return dec.Decode(variable.Map) | ||||||
| 		for _, v := range vars { | 	default: | ||||||
| 			varName := v.Name | 		return dec.Decode(variable.Str) | ||||||
|  |  | ||||||
| 			key := strings.TrimPrefix(strings.TrimPrefix(varName, name), "_") |  | ||||||
| 			newVal := reflect.New(elemType) |  | ||||||
|  |  | ||||||
| 			if err := d.DecodeVar(varName, newVal.Interface()); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			keyValue := reflect.ValueOf(key) |  | ||||||
| 			setValue.Call([]reflect.Value{keyValue, newVal.Elem()}) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		resolveValue := overridablePtr.MethodByName("Resolve") |  | ||||||
| 		if !resolveValue.IsValid() { |  | ||||||
| 			return fmt.Errorf("method Resolve not found on OverridableField") |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		names, err := overrides.Resolve(d.info, overrides.DefaultOpts) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		resolveValue.Call([]reflect.Value{reflect.ValueOf(names)}) |  | ||||||
| 		return nil |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -307,49 +246,32 @@ func (d *Decoder) getFunc(name string) *syntax.Stmt { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Decoder) getVarNoOverrides(name string) *expand.Variable { | // getVar gets a variable based on its name, taking into account | ||||||
| 	val, ok := d.Runner.Vars[name] | // override variables and nameref variables. | ||||||
| 	if ok { | func (d *Decoder) getVar(name string) *expand.Variable { | ||||||
| 		// Resolve nameref variables | 	names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name)) | ||||||
| 		_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string { | 	if err != nil { | ||||||
| 			if val, ok := d.Runner.Vars[s]; ok { | 		return nil | ||||||
| 				return val.String() | 	} | ||||||
| 			} |  | ||||||
| 			return "" |  | ||||||
| 		})) |  | ||||||
| 		val = resolved |  | ||||||
|  |  | ||||||
| 		return &val | 	for _, varName := range names { | ||||||
|  | 		val, ok := d.Runner.Vars[varName] | ||||||
|  | 		if ok { | ||||||
|  | 			// Resolve nameref variables | ||||||
|  | 			_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string { | ||||||
|  | 				if val, ok := d.Runner.Vars[s]; ok { | ||||||
|  | 					return val.String() | ||||||
|  | 				} | ||||||
|  | 				return "" | ||||||
|  | 			})) | ||||||
|  | 			val = resolved | ||||||
|  |  | ||||||
|  | 			return &val | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| type vars struct { |  | ||||||
| 	Name  string |  | ||||||
| 	Value *expand.Variable |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (d *Decoder) getVarsByPrefix(prefix string) []*vars { |  | ||||||
| 	result := make([]*vars, 0) |  | ||||||
| 	for name, val := range d.Runner.Vars { |  | ||||||
| 		if !strings.HasPrefix(name, prefix) { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		switch prefix { |  | ||||||
| 		case "auto_req": |  | ||||||
| 			if strings.HasPrefix(name, "auto_req_skiplist") { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 		case "auto_prov": |  | ||||||
| 			if strings.HasPrefix(name, "auto_prov_skiplist") { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		result = append(result, &vars{name, &val}) |  | ||||||
| 	} |  | ||||||
| 	return result |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func IsTruthy(value string) bool { | func IsTruthy(value string) bool { | ||||||
| 	value = strings.ToLower(strings.TrimSpace(value)) | 	value = strings.ToLower(strings.TrimSpace(value)) | ||||||
| 	return value == "true" || value == "yes" || value == "1" | 	return value == "true" || value == "yes" || value == "1" | ||||||
|   | |||||||
| @@ -32,25 +32,24 @@ import ( | |||||||
| 	"mvdan.cc/sh/v3/syntax" | 	"mvdan.cc/sh/v3/syntax" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type BuildVars struct { | type BuildVars struct { | ||||||
| 	Name          string                           `sh:"name,required"` | 	Name          string   `sh:"name,required"` | ||||||
| 	Version       string                           `sh:"version,required"` | 	Version       string   `sh:"version,required"` | ||||||
| 	Release       int                              `sh:"release,required"` | 	Release       int      `sh:"release,required"` | ||||||
| 	Epoch         uint                             `sh:"epoch"` | 	Epoch         uint     `sh:"epoch"` | ||||||
| 	Description   alrsh.OverridableField[string]   `sh:"desc"` | 	Description   string   `sh:"desc"` | ||||||
| 	Homepage      string                           `sh:"homepage"` | 	Homepage      string   `sh:"homepage"` | ||||||
| 	Maintainer    string                           `sh:"maintainer"` | 	Maintainer    string   `sh:"maintainer"` | ||||||
| 	Architectures []string                         `sh:"architectures"` | 	Architectures []string `sh:"architectures"` | ||||||
| 	Licenses      []string                         `sh:"license"` | 	Licenses      []string `sh:"license"` | ||||||
| 	Provides      []string                         `sh:"provides"` | 	Provides      []string `sh:"provides"` | ||||||
| 	Conflicts     []string                         `sh:"conflicts"` | 	Conflicts     []string `sh:"conflicts"` | ||||||
| 	Depends       []string                         `sh:"deps"` | 	Depends       []string `sh:"deps"` | ||||||
| 	BuildDepends  alrsh.OverridableField[[]string] `sh:"build_deps"` | 	BuildDepends  []string `sh:"build_deps"` | ||||||
| 	Replaces      alrsh.OverridableField[[]string] `sh:"replaces"` | 	Replaces      []string `sh:"replaces"` | ||||||
| } | } | ||||||
|  |  | ||||||
| const testScript = ` | const testScript = ` | ||||||
| @@ -114,34 +113,22 @@ func TestDecodeVars(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	expected := BuildVars{ | 	expected := BuildVars{ | ||||||
| 		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:   "Test package", | ||||||
| 			"": "Test package", |  | ||||||
| 		}), |  | ||||||
| 		Homepage:      "https://gitea.plemya-x.ru/xpamych/ALR", | 		Homepage:      "https://gitea.plemya-x.ru/xpamych/ALR", | ||||||
| 		Maintainer:    "Евгений Храмов <xpamych@yandex.ru>", | 		Maintainer:    "Евгений Храмов <xpamych@yandex.ru>", | ||||||
| 		Architectures: []string{"arm64", "amd64"}, | 		Architectures: []string{"arm64", "amd64"}, | ||||||
| 		Licenses:      []string{"GPL-3.0-or-later"}, | 		Licenses:      []string{"GPL-3.0-or-later"}, | ||||||
| 		Provides:      []string{"test"}, | 		Provides:      []string{"test"}, | ||||||
| 		Conflicts:     []string{"test"}, | 		Conflicts:     []string{"test"}, | ||||||
| 		Replaces: alrsh.OverridableFromMap(map[string][]string{ | 		Replaces:      []string{"test-legacy"}, | ||||||
| 			"":        {"test-old"}, | 		Depends:       []string{"sudo"}, | ||||||
| 			"test_os": {"test-legacy"}, | 		BuildDepends:  []string{"go"}, | ||||||
| 		}), |  | ||||||
| 		Depends: []string{"sudo"}, |  | ||||||
| 		BuildDepends: alrsh.OverridableFromMap(map[string][]string{ |  | ||||||
| 			"":     {"golang"}, |  | ||||||
| 			"arch": {"go"}, |  | ||||||
| 		}), |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	expected.Description.SetResolved("Test package") |  | ||||||
| 	expected.Replaces.SetResolved([]string{"test-legacy"}) |  | ||||||
| 	expected.BuildDepends.SetResolved([]string{"go"}) |  | ||||||
|  |  | ||||||
| 	if !reflect.DeepEqual(bv, expected) { | 	if !reflect.DeepEqual(bv, expected) { | ||||||
| 		t.Errorf("Expected %v, got %v", expected, bv) | 		t.Errorf("Expected %v, got %v", expected, bv) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,53 +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 helpers |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"io/fs" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // dirLfs implements fs.FS like os.DirFS but uses LStat instead of Stat. |  | ||||||
| // This means symbolic links are treated as links themselves rather than |  | ||||||
| // being followed to their targets. |  | ||||||
| type dirLfs struct { |  | ||||||
| 	fs.FS |  | ||||||
| 	dir string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewDirLFS(dir string) *dirLfs { |  | ||||||
| 	return &dirLfs{ |  | ||||||
| 		FS:  os.DirFS(dir), |  | ||||||
| 		dir: dir, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (d *dirLfs) Stat(name string) (fs.FileInfo, error) { |  | ||||||
| 	if !fs.ValidPath(name) { |  | ||||||
| 		return nil, &fs.PathError{Op: "stat", Path: name, Err: fs.ErrInvalid} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fullPath := filepath.Join(d.dir, filepath.FromSlash(name)) |  | ||||||
|  |  | ||||||
| 	info, err := os.Lstat(fullPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, &fs.PathError{Op: "stat", Path: name, Err: err} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return info, nil |  | ||||||
| } |  | ||||||
| @@ -1,179 +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 helpers |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"os" |  | ||||||
| 	"path" |  | ||||||
| 	"path/filepath" |  | ||||||
|  |  | ||||||
| 	"github.com/bmatcuk/doublestar/v4" |  | ||||||
| 	"mvdan.cc/sh/v3/interp" |  | ||||||
| 	"mvdan.cc/sh/v3/syntax" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func matchNamePattern(name, pattern string) bool { |  | ||||||
| 	matched, err := filepath.Match(pattern, name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	return matched |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func validateDir(dirPath, commandName string) error { |  | ||||||
| 	info, err := os.Stat(dirPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("%s: %w", commandName, err) |  | ||||||
| 	} |  | ||||||
| 	if !info.IsDir() { |  | ||||||
| 		return fmt.Errorf("%s: %s is not a directory", commandName, dirPath) |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func outputFiles(hc interp.HandlerContext, files []string) error { |  | ||||||
| 	for _, file := range files { |  | ||||||
| 		v, err := syntax.Quote(file, syntax.LangAuto) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		fmt.Fprintln(hc.Stdout, v) |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func makeRelativePath(basePath, fullPath string) (string, error) { |  | ||||||
| 	relPath, err := filepath.Rel(basePath, fullPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	return "./" + relPath, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func filesFindLangCmd(hc interp.HandlerContext, cmd string, args []string) error { |  | ||||||
| 	namePattern := "*.mo" |  | ||||||
| 	if len(args) > 0 { |  | ||||||
| 		namePattern = args[0] + ".mo" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	localePath := "./usr/share/locale/" |  | ||||||
| 	realPath := path.Join(hc.Dir, localePath) |  | ||||||
|  |  | ||||||
| 	if err := validateDir(realPath, "files-find-lang"); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var langFiles []string |  | ||||||
| 	err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error { |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !info.IsDir() && matchNamePattern(info.Name(), namePattern) { |  | ||||||
| 			relPath, relErr := makeRelativePath(hc.Dir, p) |  | ||||||
| 			if relErr != nil { |  | ||||||
| 				return relErr |  | ||||||
| 			} |  | ||||||
| 			langFiles = append(langFiles, relPath) |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("files-find-lang: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return outputFiles(hc, langFiles) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func filesFindDocCmd(hc interp.HandlerContext, cmd string, args []string) error { |  | ||||||
| 	namePattern := "*" |  | ||||||
| 	if len(args) > 0 { |  | ||||||
| 		namePattern = args[0] |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	docPath := "./usr/share/doc/" |  | ||||||
| 	docRealPath := path.Join(hc.Dir, docPath) |  | ||||||
|  |  | ||||||
| 	if err := validateDir(docRealPath, "files-find-doc"); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var docFiles []string |  | ||||||
|  |  | ||||||
| 	entries, err := os.ReadDir(docRealPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("files-find-doc: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, entry := range entries { |  | ||||||
| 		if matchNamePattern(entry.Name(), namePattern) { |  | ||||||
| 			targetPath := filepath.Join(docRealPath, entry.Name()) |  | ||||||
| 			targetInfo, err := os.Stat(targetPath) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return fmt.Errorf("files-find-doc: %w", err) |  | ||||||
| 			} |  | ||||||
| 			if targetInfo.IsDir() { |  | ||||||
| 				err := filepath.Walk(targetPath, func(subPath string, subInfo os.FileInfo, subErr error) error { |  | ||||||
| 					if subErr != nil { |  | ||||||
| 						return subErr |  | ||||||
| 					} |  | ||||||
| 					relPath, err := makeRelativePath(hc.Dir, subPath) |  | ||||||
| 					if err != nil { |  | ||||||
| 						return err |  | ||||||
| 					} |  | ||||||
| 					docFiles = append(docFiles, relPath) |  | ||||||
| 					return nil |  | ||||||
| 				}) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return fmt.Errorf("files-find-doc: %w", err) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return outputFiles(hc, docFiles) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func filesFindCmd(hc interp.HandlerContext, cmd string, args []string) error { |  | ||||||
| 	if len(args) == 0 { |  | ||||||
| 		return fmt.Errorf("files-find: at least one glob pattern is required") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var foundFiles []string |  | ||||||
|  |  | ||||||
| 	for _, globPattern := range args { |  | ||||||
| 		searchPath := path.Join(hc.Dir, globPattern) |  | ||||||
|  |  | ||||||
| 		basepath, pattern := doublestar.SplitPattern(searchPath) |  | ||||||
| 		fsys := NewDirLFS(basepath) |  | ||||||
| 		matches, err := doublestar.Glob(fsys, pattern, doublestar.WithNoFollow(), doublestar.WithFailOnPatternNotExist()) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("files-find: glob pattern error: %w", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		for _, match := range matches { |  | ||||||
| 			relPath, err := makeRelativePath(hc.Dir, path.Join(basepath, match)) |  | ||||||
| 			if err != nil { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			foundFiles = append(foundFiles, relPath) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return outputFiles(hc, foundFiles) |  | ||||||
| } |  | ||||||
| @@ -24,6 +24,7 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"path" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -56,7 +57,6 @@ var Helpers = handlers.ExecFuncs{ | |||||||
| 	"install-library":      installLibraryCmd, | 	"install-library":      installLibraryCmd, | ||||||
| 	"git-version":          gitVersionCmd, | 	"git-version":          gitVersionCmd, | ||||||
|  |  | ||||||
| 	"files-find":      filesFindCmd, |  | ||||||
| 	"files-find-lang": filesFindLangCmd, | 	"files-find-lang": filesFindLangCmd, | ||||||
| 	"files-find-doc":  filesFindDocCmd, | 	"files-find-doc":  filesFindDocCmd, | ||||||
| } | } | ||||||
| @@ -65,7 +65,6 @@ var Helpers = handlers.ExecFuncs{ | |||||||
| // that don't modify any state | // that don't modify any state | ||||||
| var Restricted = handlers.ExecFuncs{ | var Restricted = handlers.ExecFuncs{ | ||||||
| 	"git-version":     gitVersionCmd, | 	"git-version":     gitVersionCmd, | ||||||
| 	"files-find":      filesFindCmd, |  | ||||||
| 	"files-find-lang": filesFindLangCmd, | 	"files-find-lang": filesFindLangCmd, | ||||||
| 	"files-find-doc":  filesFindDocCmd, | 	"files-find-doc":  filesFindDocCmd, | ||||||
| } | } | ||||||
| @@ -266,6 +265,114 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func filesFindLangCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||||
|  | 	namePattern := "*.mo" | ||||||
|  | 	if len(args) > 0 { | ||||||
|  | 		namePattern = args[0] + ".mo" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	localePath := "./usr/share/locale/" | ||||||
|  | 	realPath := path.Join(hc.Dir, localePath) | ||||||
|  |  | ||||||
|  | 	info, err := os.Stat(realPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("files-find-lang: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if !info.IsDir() { | ||||||
|  | 		return fmt.Errorf("files-find-lang: %s is not a directory", localePath) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var langFiles []string | ||||||
|  | 	err = filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error { | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !info.IsDir() && matchNamePattern(info.Name(), namePattern) { | ||||||
|  | 			relPath, relErr := filepath.Rel(hc.Dir, p) | ||||||
|  | 			if relErr != nil { | ||||||
|  | 				return relErr | ||||||
|  | 			} | ||||||
|  | 			langFiles = append(langFiles, "./"+relPath) | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("files-find-lang: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, file := range langFiles { | ||||||
|  | 		fmt.Fprintln(hc.Stdout, file) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func filesFindDocCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||||
|  | 	namePattern := "*" | ||||||
|  | 	if len(args) > 0 { | ||||||
|  | 		namePattern = args[0] | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	docPath := "./usr/share/doc/" | ||||||
|  | 	docRealPath := path.Join(hc.Dir, docPath) | ||||||
|  |  | ||||||
|  | 	info, err := os.Stat(docRealPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("files-find-doc: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if !info.IsDir() { | ||||||
|  | 		return fmt.Errorf("files-find-doc: %s is not a directory", docPath) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var docFiles []string | ||||||
|  |  | ||||||
|  | 	entries, err := os.ReadDir(docRealPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	for _, entry := range entries { | ||||||
|  | 		if matchNamePattern(entry.Name(), namePattern) { | ||||||
|  | 			targetPath := filepath.Join(docRealPath, entry.Name()) | ||||||
|  | 			targetInfo, err := os.Stat(targetPath) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			if targetInfo.IsDir() { | ||||||
|  | 				err := filepath.Walk(targetPath, func(subPath string, subInfo os.FileInfo, subErr error) error { | ||||||
|  | 					relPath, err := filepath.Rel(hc.Dir, subPath) | ||||||
|  | 					if err != nil { | ||||||
|  | 						return err | ||||||
|  | 					} | ||||||
|  | 					docFiles = append(docFiles, "./"+relPath) | ||||||
|  | 					return nil | ||||||
|  | 				}) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("files-find-doc: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, file := range docFiles { | ||||||
|  | 		fmt.Fprintln(hc.Stdout, file) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func matchNamePattern(name, pattern string) bool { | ||||||
|  | 	matched, err := filepath.Match(pattern, name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return matched | ||||||
|  | } | ||||||
|  |  | ||||||
| func helperInstall(from, to string, perms os.FileMode) error { | func helperInstall(from, to string, perms os.FileMode) error { | ||||||
| 	err := os.MkdirAll(filepath.Dir(to), 0o755) | 	err := os.MkdirAll(filepath.Dir(to), 0o755) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -24,8 +24,6 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/bmatcuk/doublestar/v4" |  | ||||||
| 	"github.com/google/shlex" |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"mvdan.cc/sh/v3/interp" | 	"mvdan.cc/sh/v3/interp" | ||||||
| 	"mvdan.cc/sh/v3/syntax" | 	"mvdan.cc/sh/v3/syntax" | ||||||
| @@ -33,19 +31,12 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type symlink struct { |  | ||||||
| 	linkPath   string |  | ||||||
| 	targetPath string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type testCase struct { | type testCase struct { | ||||||
| 	name             string | 	name           string | ||||||
| 	dirsToCreate     []string | 	dirsToCreate   []string | ||||||
| 	filesToCreate    []string | 	filesToCreate  []string | ||||||
| 	expectedOutput   []string | 	expectedOutput []string | ||||||
| 	symlinksToCreate []symlink | 	args           string | ||||||
| 	args             string |  | ||||||
| 	expectedError    error |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestFindFilesDoc(t *testing.T) { | func TestFindFilesDoc(t *testing.T) { | ||||||
| @@ -134,8 +125,7 @@ files-find-doc ` + tc.args | |||||||
| 			err = runner.Run(context.Background(), script) | 			err = runner.Run(context.Background(), script) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
| 			contents, err := shlex.Split(buf.String()) | 			contents := strings.Fields(strings.TrimSpace(buf.String())) | ||||||
| 			assert.NoError(t, err) |  | ||||||
| 			assert.ElementsMatch(t, tc.expectedOutput, contents) | 			assert.ElementsMatch(t, tc.expectedOutput, contents) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -219,120 +209,7 @@ files-find-lang ` + tc.args | |||||||
| 			err = runner.Run(context.Background(), script) | 			err = runner.Run(context.Background(), script) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
| 			contents, err := shlex.Split(buf.String()) | 			contents := strings.Fields(strings.TrimSpace(buf.String())) | ||||||
| 			assert.NoError(t, err) |  | ||||||
| 			assert.ElementsMatch(t, tc.expectedOutput, contents) |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestFindFiles(t *testing.T) { |  | ||||||
| 	tests := []testCase{ |  | ||||||
| 		{ |  | ||||||
| 			name: "With file and dir symlinks", |  | ||||||
| 			dirsToCreate: []string{ |  | ||||||
| 				"usr/share/locale/ru/LC_MESSAGES", |  | ||||||
| 				"usr/share/locale/tr/LC_MESSAGES", |  | ||||||
| 				"opt/app", |  | ||||||
| 				"opt/app/internal", |  | ||||||
| 				"opt/app/with space", |  | ||||||
| 				"usr/bin", |  | ||||||
| 			}, |  | ||||||
| 			filesToCreate: []string{ |  | ||||||
| 				"usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo", |  | ||||||
| 				"usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo", |  | ||||||
| 				"usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo", |  | ||||||
| 				"opt/app/internal/test", |  | ||||||
| 				"opt/app/with space/file", |  | ||||||
| 			}, |  | ||||||
| 			symlinksToCreate: []symlink{ |  | ||||||
| 				{ |  | ||||||
| 					linkPath:   "/opt/app/etc", |  | ||||||
| 					targetPath: "/etc", |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					linkPath:   "/usr/bin/file", |  | ||||||
| 					targetPath: "/not-existing", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			expectedOutput: []string{ |  | ||||||
| 				"./usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo", |  | ||||||
| 				"./usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo", |  | ||||||
| 				"./usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo", |  | ||||||
| 				"./opt/app/etc", |  | ||||||
| 				"./opt/app/internal", |  | ||||||
| 				"./opt/app/internal/test", |  | ||||||
| 				"./opt/app/with space", |  | ||||||
| 				"./opt/app/with space/file", |  | ||||||
| 				"./usr/bin/file", |  | ||||||
| 			}, |  | ||||||
| 			args:          "\"/usr/share/locale/*/LC_MESSAGES/*.mo\" \"/opt/app/**/*\" \"/usr/bin/file\"", |  | ||||||
| 			expectedError: nil, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name:          "Not existing paths should throw error", |  | ||||||
| 			args:          "\"/opt/test/not-existing\"", |  | ||||||
| 			expectedError: doublestar.ErrPatternNotExist, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, tc := range tests { |  | ||||||
| 		t.Run(tc.name, func(t *testing.T) { |  | ||||||
| 			tempDir, err := os.MkdirTemp("", "test-files-find") |  | ||||||
| 			assert.NoError(t, err) |  | ||||||
| 			defer os.RemoveAll(tempDir) |  | ||||||
|  |  | ||||||
| 			for _, dir := range tc.dirsToCreate { |  | ||||||
| 				dirPath := filepath.Join(tempDir, dir) |  | ||||||
| 				err := os.MkdirAll(dirPath, 0o755) |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			for _, file := range tc.filesToCreate { |  | ||||||
| 				filePath := filepath.Join(tempDir, file) |  | ||||||
| 				err := os.WriteFile(filePath, []byte("test content"), 0o644) |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			for _, sl := range tc.symlinksToCreate { |  | ||||||
| 				linkFullPath := filepath.Join(tempDir, sl.linkPath) |  | ||||||
| 				targetFullPath := sl.targetPath |  | ||||||
|  |  | ||||||
| 				// make sure parent dir exists |  | ||||||
| 				err := os.MkdirAll(filepath.Dir(linkFullPath), 0o755) |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
|  |  | ||||||
| 				err = os.Symlink(targetFullPath, linkFullPath) |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			helpers := handlers.ExecFuncs{ |  | ||||||
| 				"files-find": filesFindCmd, |  | ||||||
| 			} |  | ||||||
| 			buf := &bytes.Buffer{} |  | ||||||
| 			runner, err := interp.New( |  | ||||||
| 				interp.Dir(tempDir), |  | ||||||
| 				interp.StdIO(os.Stdin, buf, os.Stderr), |  | ||||||
| 				interp.ExecHandler(helpers.ExecHandler(interp.DefaultExecHandler(1000))), |  | ||||||
| 			) |  | ||||||
| 			assert.NoError(t, err) |  | ||||||
|  |  | ||||||
| 			scriptContent := ` |  | ||||||
| shopt -s globstar |  | ||||||
| files-find ` + tc.args |  | ||||||
|  |  | ||||||
| 			script, err := syntax.NewParser().Parse(strings.NewReader(scriptContent), "") |  | ||||||
| 			assert.NoError(t, err) |  | ||||||
|  |  | ||||||
| 			err = runner.Run(context.Background(), script) |  | ||||||
| 			if tc.expectedError != nil { |  | ||||||
| 				assert.ErrorAs(t, err, &tc.expectedError) |  | ||||||
| 			} else { |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			contents, err := shlex.Split(buf.String()) |  | ||||||
| 			assert.NoError(t, err) |  | ||||||
| 			assert.ElementsMatch(t, tc.expectedOutput, contents) | 			assert.ElementsMatch(t, tc.expectedOutput, contents) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,106 +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 stats |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"context" |  | ||||||
| 	"crypto/sha256" |  | ||||||
| 	"encoding/hex" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"os" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type InstallationData struct { |  | ||||||
| 	PackageName string `json:"packageName"` |  | ||||||
| 	Version     string `json:"version,omitempty"` |  | ||||||
| 	InstallType string `json:"installType"` // "install" or "upgrade" |  | ||||||
| 	UserAgent   string `json:"userAgent"` |  | ||||||
| 	Fingerprint string `json:"fingerprint,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	apiEndpoints = []string{ |  | ||||||
| 		"https://alr.plemya-x.ru/api/packages/track-install", |  | ||||||
| 		"http://localhost:3001/api/packages/track-install", |  | ||||||
| 	} |  | ||||||
| 	userAgent = "ALR-CLI/1.0" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func generateFingerprint(packageName string) string { |  | ||||||
| 	hostname, _ := os.Hostname() |  | ||||||
| 	data := fmt.Sprintf("%s_%s_%s", hostname, packageName, time.Now().Format("2006-01-02")) |  | ||||||
| 	hash := sha256.Sum256([]byte(data)) |  | ||||||
| 	return hex.EncodeToString(hash[:]) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TrackInstallation отправляет статистику установки пакета |  | ||||||
| func TrackInstallation(ctx context.Context, packageName string, installType string) { |  | ||||||
| 	// Запускаем в отдельной горутине, чтобы не блокировать основной процесс |  | ||||||
| 	go func() { |  | ||||||
| 		data := InstallationData{ |  | ||||||
| 			PackageName: packageName, |  | ||||||
| 			InstallType: installType, |  | ||||||
| 			UserAgent:   userAgent, |  | ||||||
| 			Fingerprint: generateFingerprint(packageName), |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		jsonData, err := json.Marshal(data) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return // Тихо игнорируем ошибки - статистика не критична |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Пробуем отправить запрос к разным endpoint-ам |  | ||||||
| 		for _, endpoint := range apiEndpoints { |  | ||||||
| 			if sendRequest(endpoint, jsonData) { |  | ||||||
| 				return // Если хотя бы один запрос прошёл успешно, выходим |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func sendRequest(endpoint string, data []byte) bool { |  | ||||||
| 	client := &http.Client{ |  | ||||||
| 		Timeout: 5 * time.Second, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(data)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	req.Header.Set("Content-Type", "application/json") |  | ||||||
| 	req.Header.Set("User-Agent", userAgent) |  | ||||||
|  |  | ||||||
| 	resp, err := client.Do(req) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	defer resp.Body.Close() |  | ||||||
|  |  | ||||||
| 	return resp.StatusCode >= 200 && resp.StatusCode < 300 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ShouldTrackPackage проверяет, нужно ли отслеживать установку этого пакета |  | ||||||
| func ShouldTrackPackage(packageName string) bool { |  | ||||||
| 	// Отслеживаем только alr-bin |  | ||||||
| 	return strings.Contains(packageName, "alr-bin") |  | ||||||
| } |  | ||||||
| @@ -9,104 +9,60 @@ msgstr "" | |||||||
| "Content-Transfer-Encoding: 8bit\n" | "Content-Transfer-Encoding: 8bit\n" | ||||||
| "Plural-Forms: nplurals=2; plural=(n != 1);\n" | "Plural-Forms: nplurals=2; plural=(n != 1);\n" | ||||||
|  |  | ||||||
| #: build.go:41 | #: build.go:42 | ||||||
| msgid "Build a local package" | msgid "Build a local package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:47 | #: build.go:48 | ||||||
| msgid "Path to the build script" | msgid "Path to the build script" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:52 | #: build.go:53 | ||||||
| msgid "Specify subpackage in script (for multi package script only)" | msgid "Specify subpackage in script (for multi package script only)" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:57 | #: build.go:58 | ||||||
| msgid "Name of the package to build and its repo (example: default/go-bin)" | msgid "Name of the package to build and its repo (example: default/go-bin)" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:62 | #: build.go:63 | ||||||
| msgid "" | msgid "" | ||||||
| "Build package from scratch even if there's an already built package available" | "Build package from scratch even if there's an already built package available" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:72 | #: build.go:73 | ||||||
| msgid "Error getting working directory" | msgid "Error getting working directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:117 | #: build.go:118 | ||||||
| msgid "Cannot get absolute script path" | msgid "Cannot get absolute script path" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:143 | #: build.go:152 | ||||||
| msgid "Package not found" | msgid "Package not found" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:156 | #: build.go:165 | ||||||
| msgid "Nothing to build" | msgid "Nothing to build" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:213 | #: build.go:222 | ||||||
| msgid "Error building package" | msgid "Error building package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:220 | #: build.go:229 | ||||||
| msgid "Error moving the package" | msgid "Error moving the package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:224 | #: build.go:233 | ||||||
| msgid "Done" | msgid "Done" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: config.go:36 | #: fix.go:38 | ||||||
| msgid "Manage config" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: config.go:48 |  | ||||||
| msgid "Show config" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: config.go:84 |  | ||||||
| msgid "Set config value" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: config.go:85 |  | ||||||
| msgid "<key> <value>" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: config.go:118 config.go:126 |  | ||||||
| msgid "invalid boolean value for %s: %s" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: config.go:141 |  | ||||||
| msgid "use 'repo add/remove' commands to manage repositories" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: config.go:143 config.go:221 |  | ||||||
| msgid "unknown config key: %s" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: config.go:147 |  | ||||||
| msgid "failed to save config" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: config.go:150 |  | ||||||
| msgid "Successfully set %s = %s" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: config.go:159 |  | ||||||
| msgid "Get config value" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: config.go:160 |  | ||||||
| msgid "<key>" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: fix.go:39 |  | ||||||
| msgid "Attempt to fix problems with ALR" | msgid "Attempt to fix problems with ALR" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:60 | #: fix.go:59 | ||||||
| msgid "Clearing cache directory" | msgid "Clearing cache directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -118,15 +74,15 @@ msgstr "" | |||||||
| msgid "Unable to read cache directory contents" | msgid "Unable to read cache directory contents" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:82 | #: fix.go:76 | ||||||
| msgid "Unable to remove cache item (%s)" | msgid "Unable to remove cache item (%s)" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:86 | #: fix.go:80 | ||||||
| msgid "Rebuilding cache" | msgid "Rebuilding cache" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:90 | #: fix.go:84 | ||||||
| msgid "Unable to create new cache directory" | msgid "Unable to create new cache directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -170,124 +126,58 @@ msgstr "" | |||||||
| msgid "Error getting packages" | msgid "Error getting packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:83 | #: info.go:76 | ||||||
|  | msgid "Error iterating over packages" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: info.go:90 | ||||||
| msgid "Command info expected at least 1 argument, got %d" | msgid "Command info expected at least 1 argument, got %d" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:104 | #: info.go:110 | ||||||
| msgid "Error finding packages" | msgid "Error finding packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:118 | #: info.go:124 | ||||||
| msgid "Can't detect system language" | msgid "Can't detect system language" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:134 | #: info.go:141 | ||||||
| msgid "Error resolving overrides" | msgid "Error resolving overrides" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:143 | #: info.go:149 info.go:154 | ||||||
| msgid "Error encoding script variables" | msgid "Error encoding script variables" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:39 | #: install.go:40 | ||||||
| msgid "Install a new package" | msgid "Install a new package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:51 | #: install.go:52 | ||||||
| msgid "Command install expected at least 1 argument, got %d" | msgid "Command install expected at least 1 argument, got %d" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:113 | #: install.go:114 | ||||||
| msgid "Error when installing the package" | msgid "Error when installing the package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:151 | #: install.go:159 | ||||||
| msgid "Remove an installed package" | msgid "Remove an installed package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:170 | #: install.go:178 | ||||||
| msgid "Error listing installed packages" | msgid "Error listing installed packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:199 | #: install.go:215 | ||||||
| msgid "Command remove expected at least 1 argument, got %d" | msgid "Command remove expected at least 1 argument, got %d" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:214 | #: install.go:230 | ||||||
| msgid "Error removing packages" | msgid "Error removing packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/build/build.go:351 |  | ||||||
| msgid "Building package" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/build.go:380 |  | ||||||
| msgid "The checksums array must be the same length as sources" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/build.go:422 |  | ||||||
| msgid "Downloading sources" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/build.go:468 |  | ||||||
| msgid "Would you like to remove the build dependencies?" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/build.go:546 |  | ||||||
| msgid "Installing dependencies" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/checker.go:43 |  | ||||||
| msgid "" |  | ||||||
| "Your system's CPU architecture doesn't match this package. Do you want to " |  | ||||||
| "build anyway?" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/checker.go:67 |  | ||||||
| msgid "This package is already installed" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/find_deps/alt_linux.go:35 |  | ||||||
| msgid "Command not found on the system" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/find_deps/alt_linux.go:86 |  | ||||||
| msgid "Provided dependency found" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/find_deps/alt_linux.go:93 |  | ||||||
| msgid "Required dependency found" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/find_deps/empty.go:32 |  | ||||||
| msgid "AutoProv is not implemented for this package format, so it's skipped" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/find_deps/empty.go:37 |  | ||||||
| msgid "AutoReq is not implemented for this package format, so it's skipped" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/firejail.go:144 |  | ||||||
| msgid "Applying FireJail integration" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/script_executor.go:145 |  | ||||||
| msgid "Building package metadata" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/script_executor.go:285 |  | ||||||
| msgid "Executing prepare()" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/script_executor.go:294 |  | ||||||
| msgid "Executing build()" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/build/script_executor.go:323 internal/build/script_executor.go:343 |  | ||||||
| msgid "Executing %s()" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:75 | #: internal/cliutils/app_builder/builder.go:75 | ||||||
| msgid "Error loading config" | msgid "Error loading config" | ||||||
| msgstr "" | msgstr "" | ||||||
| @@ -296,15 +186,15 @@ msgstr "" | |||||||
| msgid "Error initialization database" | msgid "Error initialization database" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:142 | #: internal/cliutils/app_builder/builder.go:135 | ||||||
| msgid "Error pulling repositories" | msgid "Error pulling repositories" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:159 | #: internal/cliutils/app_builder/builder.go:152 | ||||||
| msgid "Error parsing os release" | msgid "Error parsing os release" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:172 | #: internal/cliutils/app_builder/builder.go:165 | ||||||
| msgid "Unable to detect a supported package manager on the system" | msgid "Unable to detect a supported package manager on the system" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -394,45 +284,43 @@ msgid "" | |||||||
| "instead!" | "instead!" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/db/db.go:76 | #: internal/db/db.go:137 | ||||||
| msgid "Database version mismatch; resetting" | msgid "Database version mismatch; resetting" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/db/db.go:82 | #: internal/db/db.go:144 | ||||||
| msgid "" | msgid "" | ||||||
| "Database version does not exist. Run alr fix if something isn't working." | "Database version does not exist. Run alr fix if something isn't working." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/dl/dl.go:170 | ||||||
|  | msgid "Source can be updated, updating if required" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/dl/dl.go:201 | ||||||
|  | msgid "Source found in cache and linked to destination" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/dl/dl.go:208 | ||||||
|  | msgid "Source updated and linked to destination" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/dl/dl.go:222 | ||||||
|  | msgid "Downloading source" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/dl/progress_tui.go:100 | ||||||
|  | msgid "%s: done!\n" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/dl/progress_tui.go:104 | ||||||
|  | msgid "%s %s downloading at %s/s\n" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: internal/logger/log.go:41 | #: internal/logger/log.go:41 | ||||||
| msgid "ERROR" | msgid "ERROR" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:97 |  | ||||||
| msgid "Trying mirror" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:103 |  | ||||||
| msgid "Failed to pull from URL" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:167 |  | ||||||
| msgid "Pulling repository" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:204 |  | ||||||
| msgid "Repository up to date" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:239 |  | ||||||
| msgid "Git repository does not appear to be a valid ALR repo" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:255 |  | ||||||
| msgid "" |  | ||||||
| "ALR repo's minimum ALR version is greater than the current version. Try " |  | ||||||
| "updating ALR if something doesn't work." |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/utils/cmd.go:97 | #: internal/utils/cmd.go:97 | ||||||
| msgid "Error on dropping capabilities" | msgid "Error on dropping capabilities" | ||||||
| msgstr "" | msgstr "" | ||||||
| @@ -445,34 +333,30 @@ msgstr "" | |||||||
| msgid "You need to be root to perform this action" | msgid "You need to be root to perform this action" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:45 | #: list.go:43 | ||||||
| msgid "List ALR repo packages" | msgid "List ALR repo packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:59 | #: list.go:57 | ||||||
| msgid "Format output using a Go template" | msgid "Format output using a Go template" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:91 | #: list.go:89 | ||||||
| msgid "Error getting packages for upgrade" | msgid "Error getting packages for upgrade" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:94 | #: list.go:92 | ||||||
| msgid "No packages for upgrade" | msgid "No packages for upgrade" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:104 list.go:201 | #: list.go:102 list.go:187 | ||||||
| msgid "Error parsing format template" | msgid "Error parsing format template" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:110 list.go:205 | #: list.go:108 list.go:191 | ||||||
| msgid "Error executing template" | msgid "Error executing template" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:164 |  | ||||||
| msgid "Failed to parse release" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: main.go:45 | #: main.go:45 | ||||||
| msgid "Print the current ALR version and exit" | msgid "Print the current ALR version and exit" | ||||||
| msgstr "" | msgstr "" | ||||||
| @@ -485,140 +369,147 @@ msgstr "" | |||||||
| msgid "Enable interactive questions and prompts" | msgid "Enable interactive questions and prompts" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: main.go:148 | #: main.go:146 | ||||||
| msgid "Show help" | msgid "Show help" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: main.go:152 | #: main.go:150 | ||||||
| msgid "Error while running app" | msgid "Error while running app" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/dl/dl.go:170 | #: pkg/build/build.go:395 | ||||||
| msgid "Source can be updated, updating if required" | msgid "Building package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/dl/dl.go:196 | #: pkg/build/build.go:424 | ||||||
| msgid "Source found in cache and linked to destination" | msgid "The checksums array must be the same length as sources" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/dl/dl.go:203 | #: pkg/build/build.go:455 | ||||||
| msgid "Source updated and linked to destination" | msgid "Downloading sources" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/dl/dl.go:217 | #: pkg/build/build.go:549 | ||||||
| msgid "Downloading source" | msgid "Installing dependencies" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/dl/progress_tui.go:100 | #: pkg/build/checker.go:43 | ||||||
| msgid "%s: done!\n" | msgid "" | ||||||
|  | "Your system's CPU architecture doesn't match this package. Do you want to " | ||||||
|  | "build anyway?" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/dl/progress_tui.go:104 | #: pkg/build/checker.go:67 | ||||||
| msgid "%s %s downloading at %s/s\n" | msgid "This package is already installed" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/find_deps/alt_linux.go:35 | ||||||
|  | msgid "Command not found on the system" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/find_deps/alt_linux.go:86 | ||||||
|  | msgid "Provided dependency found" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/find_deps/alt_linux.go:93 | ||||||
|  | msgid "Required dependency found" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/find_deps/empty.go:32 | ||||||
|  | msgid "AutoProv is not implemented for this package format, so it's skipped" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/find_deps/empty.go:37 | ||||||
|  | msgid "AutoReq is not implemented for this package format, so it's skipped" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/script_executor.go:241 | ||||||
|  | msgid "Building package metadata" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/script_executor.go:372 | ||||||
|  | msgid "Executing prepare()" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/script_executor.go:381 | ||||||
|  | msgid "Executing build()" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/script_executor.go:410 pkg/build/script_executor.go:430 | ||||||
|  | msgid "Executing %s()" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/repos/pull.go:77 | ||||||
|  | msgid "Pulling repository" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/repos/pull.go:113 | ||||||
|  | msgid "Repository up to date" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/repos/pull.go:204 | ||||||
|  | msgid "Git repository does not appear to be a valid ALR repo" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/repos/pull.go:220 | ||||||
|  | msgid "" | ||||||
|  | "ALR repo's minimum ALR version is greater than the current version. Try " | ||||||
|  | "updating ALR if something doesn't work." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: refresh.go:30 | #: refresh.go:30 | ||||||
| msgid "Pull all repositories that have changed" | msgid "Pull all repositories that have changed" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:42 | #: repo.go:39 | ||||||
| msgid "Manage repos" | msgid "Manage repos" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:56 repo.go:625 | #: repo.go:50 repo.go:220 | ||||||
| msgid "Remove an existing repository" | msgid "Remove an existing repository" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:58 repo.go:521 | #: repo.go:52 | ||||||
| msgid "<name>" | msgid "<name>" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:103 repo.go:465 repo.go:568 | #: repo.go:82 | ||||||
| msgid "Repo \"%s\" does not exist" | msgid "Repo \"%s\" does not exist" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:110 | #: repo.go:89 | ||||||
| msgid "Error removing repo directory" | msgid "Error removing repo directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:114 repo.go:195 repo.go:253 repo.go:316 repo.go:389 repo.go:504 | #: repo.go:93 repo.go:160 | ||||||
| #: repo.go:576 |  | ||||||
| msgid "Error saving config" | msgid "Error saving config" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:133 | #: repo.go:112 | ||||||
| msgid "Error removing packages from database" | msgid "Error removing packages from database" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:144 repo.go:595 | #: repo.go:123 repo.go:190 | ||||||
| msgid "Add a new repository" | msgid "Add a new repository" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:145 repo.go:270 repo.go:345 repo.go:402 | #: repo.go:124 | ||||||
| msgid "<name> <url>" | msgid "<name> <url>" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:170 | #: repo.go:149 | ||||||
| msgid "Repo \"%s\" already exists" | msgid "Repo \"%s\" already exists" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:206 | #: repo.go:197 | ||||||
| msgid "Set the reference of the repository" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:207 |  | ||||||
| msgid "<name> <ref>" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:269 |  | ||||||
| msgid "Set the main url of the repository" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:332 |  | ||||||
| msgid "Manage mirrors of repos" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:344 |  | ||||||
| msgid "Add a mirror URL to repository" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:401 |  | ||||||
| msgid "Remove mirror from the repository" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:420 |  | ||||||
| msgid "Ignore if mirror does not exist" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:425 |  | ||||||
| msgid "Match partial URL (e.g., github.com instead of full URL)" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:490 |  | ||||||
| msgid "No mirrors containing \"%s\" found in repo \"%s\"" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:492 |  | ||||||
| msgid "URL \"%s\" does not exist in repo \"%s\"" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:508 repo.go:580 |  | ||||||
| msgid "Removed %d mirrors from repo \"%s\"\n" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:520 |  | ||||||
| msgid "Remove all mirrors from the repository" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: repo.go:602 |  | ||||||
| msgid "Name of the new repo" | msgid "Name of the new repo" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:608 | #: repo.go:203 | ||||||
| msgid "URL of the new repo" | msgid "URL of the new repo" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:632 | #: repo.go:227 | ||||||
| msgid "Name of the repo to be deleted" | msgid "Name of the repo to be deleted" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -646,14 +537,14 @@ msgstr "" | |||||||
| msgid "Error while executing search" | msgid "Error while executing search" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: upgrade.go:48 | #: upgrade.go:47 | ||||||
| msgid "Upgrade all installed packages" | msgid "Upgrade all installed packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: upgrade.go:106 upgrade.go:123 | #: upgrade.go:105 upgrade.go:122 | ||||||
| msgid "Error checking for updates" | msgid "Error checking for updates" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: upgrade.go:126 | #: upgrade.go:125 | ||||||
| msgid "There is nothing to do." | msgid "There is nothing to do." | ||||||
| msgstr "" | msgstr "" | ||||||
|   | |||||||
| @@ -5,115 +5,71 @@ | |||||||
| msgid "" | msgid "" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: unnamed project\n" | "Project-Id-Version: unnamed project\n" | ||||||
| "PO-Revision-Date: 2025-07-09 20:38+0300\n" | "PO-Revision-Date: 2025-05-13 23:24+0300\n" | ||||||
| "Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n" | "Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n" | ||||||
| "Language-Team: Russian\n" | "Language-Team: Russian\n" | ||||||
| "Language: ru\n" | "Language: ru\n" | ||||||
| "MIME-Version: 1.0\n" | "MIME-Version: 1.0\n" | ||||||
| "Content-Type: text/plain; charset=UTF-8\n" | "Content-Type: text/plain; charset=UTF-8\n" | ||||||
| "Content-Transfer-Encoding: 8bit\n" | "Content-Transfer-Encoding: 8bit\n" | ||||||
| "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" | ||||||
| "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | ||||||
| "X-Generator: Gtranslator 48.0\n" | "X-Generator: Gtranslator 48.0\n" | ||||||
|  |  | ||||||
| #: build.go:41 | #: build.go:42 | ||||||
| msgid "Build a local package" | msgid "Build a local package" | ||||||
| msgstr "Сборка локального пакета" | msgstr "Сборка локального пакета" | ||||||
|  |  | ||||||
| #: build.go:47 | #: build.go:48 | ||||||
| msgid "Path to the build script" | msgid "Path to the build script" | ||||||
| msgstr "Путь к скрипту сборки" | msgstr "Путь к скрипту сборки" | ||||||
|  |  | ||||||
| #: build.go:52 | #: build.go:53 | ||||||
| msgid "Specify subpackage in script (for multi package script only)" | msgid "Specify subpackage in script (for multi package script only)" | ||||||
| msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)" | msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)" | ||||||
|  |  | ||||||
| #: build.go:57 | #: build.go:58 | ||||||
| msgid "Name of the package to build and its repo (example: default/go-bin)" | msgid "Name of the package to build and its repo (example: default/go-bin)" | ||||||
| msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)" | msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)" | ||||||
|  |  | ||||||
| #: build.go:62 | #: build.go:63 | ||||||
| msgid "" | msgid "" | ||||||
| "Build package from scratch even if there's an already built package available" | "Build package from scratch even if there's an already built package available" | ||||||
| msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет" | msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет" | ||||||
|  |  | ||||||
| #: build.go:72 | #: build.go:73 | ||||||
| msgid "Error getting working directory" | msgid "Error getting working directory" | ||||||
| msgstr "Ошибка при получении рабочего каталога" | msgstr "Ошибка при получении рабочего каталога" | ||||||
|  |  | ||||||
| #: build.go:117 | #: build.go:118 | ||||||
| msgid "Cannot get absolute script path" | msgid "Cannot get absolute script path" | ||||||
| msgstr "Невозможно получить абсолютный путь к скрипту" | msgstr "Невозможно получить абсолютный путь к скрипту" | ||||||
|  |  | ||||||
| #: build.go:143 | #: build.go:152 | ||||||
| msgid "Package not found" | msgid "Package not found" | ||||||
| msgstr "Пакет не найден" | msgstr "Пакет не найден" | ||||||
|  |  | ||||||
| #: build.go:156 | #: build.go:165 | ||||||
| msgid "Nothing to build" | msgid "Nothing to build" | ||||||
| msgstr "Нечего собирать" | msgstr "Нечего собирать" | ||||||
|  |  | ||||||
| #: build.go:213 | #: build.go:222 | ||||||
| msgid "Error building package" | msgid "Error building package" | ||||||
| msgstr "Ошибка при сборке пакета" | msgstr "Ошибка при сборке пакета" | ||||||
|  |  | ||||||
| #: build.go:220 | #: build.go:229 | ||||||
| msgid "Error moving the package" | msgid "Error moving the package" | ||||||
| msgstr "Ошибка при перемещении пакета" | msgstr "Ошибка при перемещении пакета" | ||||||
|  |  | ||||||
| #: build.go:224 | #: build.go:233 | ||||||
| msgid "Done" | msgid "Done" | ||||||
| msgstr "Сделано" | msgstr "Сделано" | ||||||
|  |  | ||||||
| #: config.go:36 | #: fix.go:38 | ||||||
| msgid "Manage config" |  | ||||||
| msgstr "Управление конфигурацией" |  | ||||||
|  |  | ||||||
| #: config.go:48 |  | ||||||
| msgid "Show config" |  | ||||||
| msgstr "Показать конфигурацию" |  | ||||||
|  |  | ||||||
| #: config.go:84 |  | ||||||
| msgid "Set config value" |  | ||||||
| msgstr "Установить значение в конфигурации" |  | ||||||
|  |  | ||||||
| #: config.go:85 |  | ||||||
| msgid "<key> <value>" |  | ||||||
| msgstr "<ключ> <значение>" |  | ||||||
|  |  | ||||||
| #: config.go:118 config.go:126 |  | ||||||
| msgid "invalid boolean value for %s: %s" |  | ||||||
| msgstr "неверное булево значение для %s: %s" |  | ||||||
|  |  | ||||||
| #: config.go:141 |  | ||||||
| msgid "use 'repo add/remove' commands to manage repositories" |  | ||||||
| msgstr "используйте команды 'repo add/remove' для управления репозиториями" |  | ||||||
|  |  | ||||||
| #: config.go:143 config.go:221 |  | ||||||
| msgid "unknown config key: %s" |  | ||||||
| msgstr "неизвестный ключ конфигурации: %s" |  | ||||||
|  |  | ||||||
| #: config.go:147 |  | ||||||
| msgid "failed to save config" |  | ||||||
| msgstr "не удалось сохранить конфигурацию" |  | ||||||
|  |  | ||||||
| #: config.go:150 |  | ||||||
| msgid "Successfully set %s = %s" |  | ||||||
| msgstr "Успешно установлено %s = %s" |  | ||||||
|  |  | ||||||
| #: config.go:159 |  | ||||||
| msgid "Get config value" |  | ||||||
| msgstr "Получить значение из конфигурации" |  | ||||||
|  |  | ||||||
| #: config.go:160 |  | ||||||
| msgid "<key>" |  | ||||||
| msgstr "<ключ>" |  | ||||||
|  |  | ||||||
| #: fix.go:39 |  | ||||||
| msgid "Attempt to fix problems with ALR" | msgid "Attempt to fix problems with ALR" | ||||||
| msgstr "Попытка устранить проблемы с ALR" | msgstr "Попытка устранить проблемы с ALR" | ||||||
|  |  | ||||||
| #: fix.go:60 | #: fix.go:59 | ||||||
| msgid "Clearing cache directory" | msgid "Clearing cache directory" | ||||||
| msgstr "Очистка каталога кэша" | msgstr "Очистка каталога кэша" | ||||||
|  |  | ||||||
| @@ -125,15 +81,15 @@ msgstr "Невозможно открыть каталог кэша" | |||||||
| msgid "Unable to read cache directory contents" | msgid "Unable to read cache directory contents" | ||||||
| msgstr "Невозможно прочитать содержимое каталога кэша" | msgstr "Невозможно прочитать содержимое каталога кэша" | ||||||
|  |  | ||||||
| #: fix.go:82 | #: fix.go:76 | ||||||
| msgid "Unable to remove cache item (%s)" | msgid "Unable to remove cache item (%s)" | ||||||
| msgstr "Невозможно удалить элемент кэша (%s)" | msgstr "Невозможно удалить элемент кэша (%s)" | ||||||
|  |  | ||||||
| #: fix.go:86 | #: fix.go:80 | ||||||
| msgid "Rebuilding cache" | msgid "Rebuilding cache" | ||||||
| msgstr "Восстановление кэша" | msgstr "Восстановление кэша" | ||||||
|  |  | ||||||
| #: fix.go:90 | #: fix.go:84 | ||||||
| msgid "Unable to create new cache directory" | msgid "Unable to create new cache directory" | ||||||
| msgstr "Не удалось создать новый каталог кэша" | msgstr "Не удалось создать новый каталог кэша" | ||||||
|  |  | ||||||
| @@ -177,128 +133,58 @@ msgstr "Показывать всю информацию, не только дл | |||||||
| msgid "Error getting packages" | msgid "Error getting packages" | ||||||
| msgstr "Ошибка при получении пакетов" | msgstr "Ошибка при получении пакетов" | ||||||
|  |  | ||||||
| #: info.go:83 | #: info.go:76 | ||||||
|  | msgid "Error iterating over packages" | ||||||
|  | msgstr "Ошибка при переборе пакетов" | ||||||
|  |  | ||||||
|  | #: info.go:90 | ||||||
| msgid "Command info expected at least 1 argument, got %d" | msgid "Command info expected at least 1 argument, got %d" | ||||||
| msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" | msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" | ||||||
|  |  | ||||||
| #: info.go:104 | #: info.go:110 | ||||||
| msgid "Error finding packages" | msgid "Error finding packages" | ||||||
| msgstr "Ошибка при поиске пакетов" | msgstr "Ошибка при поиске пакетов" | ||||||
|  |  | ||||||
| #: info.go:118 | #: info.go:124 | ||||||
| msgid "Can't detect system language" | msgid "Can't detect system language" | ||||||
| msgstr "Ошибка при определении языка системы" | msgstr "Ошибка при определении языка системы" | ||||||
|  |  | ||||||
| #: info.go:134 | #: info.go:141 | ||||||
| msgid "Error resolving overrides" | msgid "Error resolving overrides" | ||||||
| msgstr "Ошибка устранения переорпеделений" | msgstr "Ошибка устранения переорпеделений" | ||||||
|  |  | ||||||
| #: info.go:143 | #: info.go:149 info.go:154 | ||||||
| msgid "Error encoding script variables" | msgid "Error encoding script variables" | ||||||
| msgstr "Ошибка кодирования переменных скрита" | msgstr "Ошибка кодирования переменных скрита" | ||||||
|  |  | ||||||
| #: install.go:39 | #: install.go:40 | ||||||
| msgid "Install a new package" | msgid "Install a new package" | ||||||
| msgstr "Установить новый пакет" | msgstr "Установить новый пакет" | ||||||
|  |  | ||||||
| #: install.go:51 | #: install.go:52 | ||||||
| msgid "Command install expected at least 1 argument, got %d" | msgid "Command install expected at least 1 argument, got %d" | ||||||
| msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" | msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" | ||||||
|  |  | ||||||
| #: install.go:113 | #: install.go:114 | ||||||
| msgid "Error when installing the package" | msgid "Error when installing the package" | ||||||
| msgstr "Ошибка при установке пакета" | msgstr "Ошибка при установке пакета" | ||||||
|  |  | ||||||
| #: install.go:151 | #: install.go:159 | ||||||
| msgid "Remove an installed package" | msgid "Remove an installed package" | ||||||
| msgstr "Удалить установленный пакет" | msgstr "Удалить установленный пакет" | ||||||
|  |  | ||||||
| #: install.go:170 | #: install.go:178 | ||||||
| msgid "Error listing installed packages" | msgid "Error listing installed packages" | ||||||
| msgstr "Ошибка при составлении списка установленных пакетов" | msgstr "Ошибка при составлении списка установленных пакетов" | ||||||
|  |  | ||||||
| #: install.go:199 | #: install.go:215 | ||||||
| msgid "Command remove expected at least 1 argument, got %d" | msgid "Command remove expected at least 1 argument, got %d" | ||||||
| msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d" | msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d" | ||||||
|  |  | ||||||
| #: install.go:214 | #: install.go:230 | ||||||
| msgid "Error removing packages" | msgid "Error removing packages" | ||||||
| msgstr "Ошибка при удалении пакетов" | msgstr "Ошибка при удалении пакетов" | ||||||
|  |  | ||||||
| #: internal/build/build.go:351 |  | ||||||
| msgid "Building package" |  | ||||||
| msgstr "Сборка пакета" |  | ||||||
|  |  | ||||||
| #: internal/build/build.go:380 |  | ||||||
| msgid "The checksums array must be the same length as sources" |  | ||||||
| msgstr "Массив контрольных сумм должен быть той же длины, что и источники" |  | ||||||
|  |  | ||||||
| #: internal/build/build.go:422 |  | ||||||
| msgid "Downloading sources" |  | ||||||
| msgstr "Скачивание источников" |  | ||||||
|  |  | ||||||
| #: internal/build/build.go:468 |  | ||||||
| msgid "Would you like to remove the build dependencies?" |  | ||||||
| msgstr "Хотели бы вы удалить зависимости сборки?" |  | ||||||
|  |  | ||||||
| #: internal/build/build.go:546 |  | ||||||
| msgid "Installing dependencies" |  | ||||||
| msgstr "Установка зависимостей" |  | ||||||
|  |  | ||||||
| #: internal/build/checker.go:43 |  | ||||||
| msgid "" |  | ||||||
| "Your system's CPU architecture doesn't match this package. Do you want to " |  | ||||||
| "build anyway?" |  | ||||||
| msgstr "" |  | ||||||
| "Архитектура процессора вашей системы не соответствует этому пакету. Вы все " |  | ||||||
| "равно хотите выполнить сборку?" |  | ||||||
|  |  | ||||||
| #: internal/build/checker.go:67 |  | ||||||
| msgid "This package is already installed" |  | ||||||
| msgstr "Этот пакет уже установлен" |  | ||||||
|  |  | ||||||
| #: internal/build/find_deps/alt_linux.go:35 |  | ||||||
| msgid "Command not found on the system" |  | ||||||
| msgstr "Команда не найдена в системе" |  | ||||||
|  |  | ||||||
| #: internal/build/find_deps/alt_linux.go:86 |  | ||||||
| msgid "Provided dependency found" |  | ||||||
| msgstr "Найденная предоставленная зависимость" |  | ||||||
|  |  | ||||||
| #: internal/build/find_deps/alt_linux.go:93 |  | ||||||
| msgid "Required dependency found" |  | ||||||
| msgstr "Найдена требуемая зависимость" |  | ||||||
|  |  | ||||||
| #: internal/build/find_deps/empty.go:32 |  | ||||||
| msgid "AutoProv is not implemented for this package format, so it's skipped" |  | ||||||
| msgstr "" |  | ||||||
| "AutoProv не реализовано для этого формата пакета, поэтому будет пропущено" |  | ||||||
|  |  | ||||||
| #: internal/build/find_deps/empty.go:37 |  | ||||||
| msgid "AutoReq is not implemented for this package format, so it's skipped" |  | ||||||
| msgstr "" |  | ||||||
| "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" |  | ||||||
|  |  | ||||||
| #: internal/build/firejail.go:144 |  | ||||||
| msgid "Applying FireJail integration" |  | ||||||
| msgstr "Применение интеграции FireJail" |  | ||||||
|  |  | ||||||
| #: internal/build/script_executor.go:145 |  | ||||||
| msgid "Building package metadata" |  | ||||||
| msgstr "Сборка метаданных пакета" |  | ||||||
|  |  | ||||||
| #: internal/build/script_executor.go:285 |  | ||||||
| msgid "Executing prepare()" |  | ||||||
| msgstr "Выполнение prepare()" |  | ||||||
|  |  | ||||||
| #: internal/build/script_executor.go:294 |  | ||||||
| msgid "Executing build()" |  | ||||||
| msgstr "Выполнение build()" |  | ||||||
|  |  | ||||||
| #: internal/build/script_executor.go:323 internal/build/script_executor.go:343 |  | ||||||
| msgid "Executing %s()" |  | ||||||
| msgstr "Выполнение %s()" |  | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:75 | #: internal/cliutils/app_builder/builder.go:75 | ||||||
| msgid "Error loading config" | msgid "Error loading config" | ||||||
| msgstr "Ошибка при загрузке" | msgstr "Ошибка при загрузке" | ||||||
| @@ -307,15 +193,15 @@ msgstr "Ошибка при загрузке" | |||||||
| msgid "Error initialization database" | msgid "Error initialization database" | ||||||
| msgstr "Ошибка инициализации базы данных" | msgstr "Ошибка инициализации базы данных" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:142 | #: internal/cliutils/app_builder/builder.go:135 | ||||||
| msgid "Error pulling repositories" | msgid "Error pulling repositories" | ||||||
| msgstr "Ошибка при извлечении репозиториев" | msgstr "Ошибка при извлечении репозиториев" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:159 | #: internal/cliutils/app_builder/builder.go:152 | ||||||
| msgid "Error parsing os release" | msgid "Error parsing os release" | ||||||
| msgstr "Ошибка при разборе файла выпуска операционной системы" | msgstr "Ошибка при разборе файла выпуска операционной системы" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:172 | #: internal/cliutils/app_builder/builder.go:165 | ||||||
| msgid "Unable to detect a supported package manager on the system" | msgid "Unable to detect a supported package manager on the system" | ||||||
| msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" | msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" | ||||||
|  |  | ||||||
| @@ -404,51 +290,45 @@ msgid "" | |||||||
| "This command is deprecated and would be removed in the future, use \"%s\" " | "This command is deprecated and would be removed in the future, use \"%s\" " | ||||||
| "instead!" | "instead!" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Эта команда устарела и будет удалена в будущем, используйте вместо нее " |  | ||||||
| "\"%s\"!" |  | ||||||
|  |  | ||||||
| #: internal/db/db.go:76 | #: internal/db/db.go:137 | ||||||
| msgid "Database version mismatch; resetting" | msgid "Database version mismatch; resetting" | ||||||
| msgstr "Несоответствие версий базы данных; сброс настроек" | msgstr "Несоответствие версий базы данных; сброс настроек" | ||||||
|  |  | ||||||
| #: internal/db/db.go:82 | #: internal/db/db.go:144 | ||||||
| msgid "" | msgid "" | ||||||
| "Database version does not exist. Run alr fix if something isn't working." | "Database version does not exist. Run alr fix if something isn't working." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Версия базы данных не существует. Запустите alr fix, если что-то не работает." | "Версия базы данных не существует. Запустите alr fix, если что-то не работает." | ||||||
|  |  | ||||||
|  | #: internal/dl/dl.go:170 | ||||||
|  | msgid "Source can be updated, updating if required" | ||||||
|  | msgstr "Исходный код можно обновлять, обновляя при необходимости" | ||||||
|  |  | ||||||
|  | #: internal/dl/dl.go:201 | ||||||
|  | msgid "Source found in cache and linked to destination" | ||||||
|  | msgstr "Источник найден в кэше и связан с пунктом назначения" | ||||||
|  |  | ||||||
|  | #: internal/dl/dl.go:208 | ||||||
|  | msgid "Source updated and linked to destination" | ||||||
|  | msgstr "Источник обновлён и связан с пунктом назначения" | ||||||
|  |  | ||||||
|  | #: internal/dl/dl.go:222 | ||||||
|  | msgid "Downloading source" | ||||||
|  | msgstr "Скачивание источника" | ||||||
|  |  | ||||||
|  | #: internal/dl/progress_tui.go:100 | ||||||
|  | msgid "%s: done!\n" | ||||||
|  | msgstr "%s: выполнено!\n" | ||||||
|  |  | ||||||
|  | #: internal/dl/progress_tui.go:104 | ||||||
|  | msgid "%s %s downloading at %s/s\n" | ||||||
|  | msgstr "%s %s загружается — %s/с\n" | ||||||
|  |  | ||||||
| #: internal/logger/log.go:41 | #: internal/logger/log.go:41 | ||||||
| msgid "ERROR" | msgid "ERROR" | ||||||
| msgstr "ОШИБКА" | msgstr "ОШИБКА" | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:97 |  | ||||||
| msgid "Trying mirror" |  | ||||||
| msgstr "Пробую зеркало" |  | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:103 |  | ||||||
| msgid "Failed to pull from URL" |  | ||||||
| msgstr "Не удалось извлечь из URL" |  | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:167 |  | ||||||
| msgid "Pulling repository" |  | ||||||
| msgstr "Скачивание репозитория" |  | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:204 |  | ||||||
| msgid "Repository up to date" |  | ||||||
| msgstr "Репозиторий уже обновлён" |  | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:239 |  | ||||||
| msgid "Git repository does not appear to be a valid ALR repo" |  | ||||||
| msgstr "Репозиторий Git не поддерживается репозиторием ALR" |  | ||||||
|  |  | ||||||
| #: internal/repos/pull.go:255 |  | ||||||
| msgid "" |  | ||||||
| "ALR repo's minimum ALR version is greater than the current version. Try " |  | ||||||
| "updating ALR if something doesn't work." |  | ||||||
| msgstr "" |  | ||||||
| "Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте " |  | ||||||
| "обновить ALR, если что-то не работает." |  | ||||||
|  |  | ||||||
| #: internal/utils/cmd.go:97 | #: internal/utils/cmd.go:97 | ||||||
| msgid "Error on dropping capabilities" | msgid "Error on dropping capabilities" | ||||||
| msgstr "Ошибка при понижении привилегий" | msgstr "Ошибка при понижении привилегий" | ||||||
| @@ -461,34 +341,30 @@ msgstr "Вы должны быть членом %s чтобы выполнить | |||||||
| msgid "You need to be root to perform this action" | msgid "You need to be root to perform this action" | ||||||
| msgstr "Вы должны быть root чтобы выполнить это" | msgstr "Вы должны быть root чтобы выполнить это" | ||||||
|  |  | ||||||
| #: list.go:45 | #: list.go:43 | ||||||
| msgid "List ALR repo packages" | msgid "List ALR repo packages" | ||||||
| msgstr "Список пакетов репозитория ALR" | msgstr "Список пакетов репозитория ALR" | ||||||
|  |  | ||||||
| #: list.go:59 | #: list.go:57 | ||||||
| msgid "Format output using a Go template" | msgid "Format output using a Go template" | ||||||
| msgstr "Формат выходных данных с использованием шаблона Go" | msgstr "Формат выходных данных с использованием шаблона Go" | ||||||
|  |  | ||||||
| #: list.go:91 | #: list.go:89 | ||||||
| msgid "Error getting packages for upgrade" | msgid "Error getting packages for upgrade" | ||||||
| msgstr "Ошибка при получении пакетов для обновления" | msgstr "Ошибка при получении пакетов для обновления" | ||||||
|  |  | ||||||
| #: list.go:94 | #: list.go:92 | ||||||
| msgid "No packages for upgrade" | msgid "No packages for upgrade" | ||||||
| msgstr "Нет пакетов к обновлению" | msgstr "Нет пакетов к обновлению" | ||||||
|  |  | ||||||
| #: list.go:104 list.go:201 | #: list.go:102 list.go:187 | ||||||
| msgid "Error parsing format template" | msgid "Error parsing format template" | ||||||
| msgstr "Ошибка при разборе шаблона" | msgstr "Ошибка при разборе шаблона" | ||||||
|  |  | ||||||
| #: list.go:110 list.go:205 | #: list.go:108 list.go:191 | ||||||
| msgid "Error executing template" | msgid "Error executing template" | ||||||
| msgstr "Ошибка при выполнении шаблона" | msgstr "Ошибка при выполнении шаблона" | ||||||
|  |  | ||||||
| #: list.go:164 |  | ||||||
| msgid "Failed to parse release" |  | ||||||
| msgstr "Не удалось разобрать релиз" |  | ||||||
|  |  | ||||||
| #: main.go:45 | #: main.go:45 | ||||||
| msgid "Print the current ALR version and exit" | msgid "Print the current ALR version and exit" | ||||||
| msgstr "Показать текущую версию ALR и выйти" | msgstr "Показать текущую версию ALR и выйти" | ||||||
| @@ -501,140 +377,153 @@ msgstr "Аргументы, которые будут переданы мене | |||||||
| msgid "Enable interactive questions and prompts" | msgid "Enable interactive questions and prompts" | ||||||
| msgstr "Включение интерактивных вопросов и запросов" | msgstr "Включение интерактивных вопросов и запросов" | ||||||
|  |  | ||||||
| #: main.go:148 | #: main.go:146 | ||||||
| msgid "Show help" | msgid "Show help" | ||||||
| msgstr "Показать справку" | msgstr "Показать справку" | ||||||
|  |  | ||||||
| #: main.go:152 | #: main.go:150 | ||||||
| msgid "Error while running app" | msgid "Error while running app" | ||||||
| msgstr "Ошибка при запуске приложения" | msgstr "Ошибка при запуске приложения" | ||||||
|  |  | ||||||
| #: pkg/dl/dl.go:170 | #: pkg/build/build.go:395 | ||||||
| msgid "Source can be updated, updating if required" | msgid "Building package" | ||||||
| msgstr "Исходный код можно обновлять, обновляя при необходимости" | msgstr "Сборка пакета" | ||||||
|  |  | ||||||
| #: pkg/dl/dl.go:196 | #: pkg/build/build.go:424 | ||||||
| msgid "Source found in cache and linked to destination" | msgid "The checksums array must be the same length as sources" | ||||||
| msgstr "Источник найден в кэше и связан с пунктом назначения" | msgstr "Массив контрольных сумм должен быть той же длины, что и источники" | ||||||
|  |  | ||||||
| #: pkg/dl/dl.go:203 | #: pkg/build/build.go:455 | ||||||
| msgid "Source updated and linked to destination" | msgid "Downloading sources" | ||||||
| msgstr "Источник обновлён и связан с пунктом назначения" | msgstr "Скачивание источников" | ||||||
|  |  | ||||||
| #: pkg/dl/dl.go:217 | #: pkg/build/build.go:549 | ||||||
| msgid "Downloading source" | msgid "Installing dependencies" | ||||||
| msgstr "Скачивание источника" | msgstr "Установка зависимостей" | ||||||
|  |  | ||||||
| #: pkg/dl/progress_tui.go:100 | #: pkg/build/checker.go:43 | ||||||
| msgid "%s: done!\n" | msgid "" | ||||||
| msgstr "%s: выполнено!\n" | "Your system's CPU architecture doesn't match this package. Do you want to " | ||||||
|  | "build anyway?" | ||||||
|  | msgstr "" | ||||||
|  | "Архитектура процессора вашей системы не соответствует этому пакету. Вы все " | ||||||
|  | "равно хотите выполнить сборку?" | ||||||
|  |  | ||||||
| #: pkg/dl/progress_tui.go:104 | #: pkg/build/checker.go:67 | ||||||
| msgid "%s %s downloading at %s/s\n" | msgid "This package is already installed" | ||||||
| msgstr "%s %s загружается — %s/с\n" | msgstr "Этот пакет уже установлен" | ||||||
|  |  | ||||||
|  | #: pkg/build/find_deps/alt_linux.go:35 | ||||||
|  | msgid "Command not found on the system" | ||||||
|  | msgstr "Команда не найдена в системе" | ||||||
|  |  | ||||||
|  | #: pkg/build/find_deps/alt_linux.go:86 | ||||||
|  | msgid "Provided dependency found" | ||||||
|  | msgstr "Найденная предоставленная зависимость" | ||||||
|  |  | ||||||
|  | #: pkg/build/find_deps/alt_linux.go:93 | ||||||
|  | msgid "Required dependency found" | ||||||
|  | msgstr "Найдена требуемая зависимость" | ||||||
|  |  | ||||||
|  | #: pkg/build/find_deps/empty.go:32 | ||||||
|  | msgid "AutoProv is not implemented for this package format, so it's skipped" | ||||||
|  | msgstr "" | ||||||
|  | "AutoProv не реализовано для этого формата пакета, поэтому будет пропущено" | ||||||
|  |  | ||||||
|  | #: pkg/build/find_deps/empty.go:37 | ||||||
|  | msgid "AutoReq is not implemented for this package format, so it's skipped" | ||||||
|  | msgstr "" | ||||||
|  | "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" | ||||||
|  |  | ||||||
|  | #: pkg/build/script_executor.go:241 | ||||||
|  | msgid "Building package metadata" | ||||||
|  | msgstr "Сборка метаданных пакета" | ||||||
|  |  | ||||||
|  | #: pkg/build/script_executor.go:372 | ||||||
|  | msgid "Executing prepare()" | ||||||
|  | msgstr "Выполнение prepare()" | ||||||
|  |  | ||||||
|  | #: pkg/build/script_executor.go:381 | ||||||
|  | msgid "Executing build()" | ||||||
|  | msgstr "Выполнение build()" | ||||||
|  |  | ||||||
|  | #: pkg/build/script_executor.go:410 pkg/build/script_executor.go:430 | ||||||
|  | msgid "Executing %s()" | ||||||
|  | msgstr "Выполнение %s()" | ||||||
|  |  | ||||||
|  | #: pkg/repos/pull.go:77 | ||||||
|  | msgid "Pulling repository" | ||||||
|  | msgstr "Скачивание репозитория" | ||||||
|  |  | ||||||
|  | #: pkg/repos/pull.go:113 | ||||||
|  | msgid "Repository up to date" | ||||||
|  | msgstr "Репозиторий уже обновлён" | ||||||
|  |  | ||||||
|  | #: pkg/repos/pull.go:204 | ||||||
|  | msgid "Git repository does not appear to be a valid ALR repo" | ||||||
|  | msgstr "Репозиторий Git не поддерживается репозиторием ALR" | ||||||
|  |  | ||||||
|  | #: pkg/repos/pull.go:220 | ||||||
|  | msgid "" | ||||||
|  | "ALR repo's minimum ALR version is greater than the current version. Try " | ||||||
|  | "updating ALR if something doesn't work." | ||||||
|  | msgstr "" | ||||||
|  | "Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте " | ||||||
|  | "обновить ALR, если что-то не работает." | ||||||
|  |  | ||||||
| #: refresh.go:30 | #: refresh.go:30 | ||||||
| msgid "Pull all repositories that have changed" | msgid "Pull all repositories that have changed" | ||||||
| msgstr "Скачать все изменённые репозитории" | msgstr "Скачать все изменённые репозитории" | ||||||
|  |  | ||||||
| #: repo.go:42 | #: repo.go:39 | ||||||
| msgid "Manage repos" | msgid "Manage repos" | ||||||
| msgstr "Управление репозиториями" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:56 repo.go:625 | #: repo.go:50 repo.go:220 | ||||||
| msgid "Remove an existing repository" | msgid "Remove an existing repository" | ||||||
| msgstr "Удалить существующий репозиторий" | msgstr "Удалить существующий репозиторий" | ||||||
|  |  | ||||||
| #: repo.go:58 repo.go:521 | #: repo.go:52 | ||||||
| msgid "<name>" | msgid "<name>" | ||||||
| msgstr "<имя>" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:103 repo.go:465 repo.go:568 | #: repo.go:82 | ||||||
| msgid "Repo \"%s\" does not exist" | msgid "Repo \"%s\" does not exist" | ||||||
| msgstr "Репозитория \"%s\" не существует" | msgstr "Репозитория \"%s\" не существует" | ||||||
|  |  | ||||||
| #: repo.go:110 | #: repo.go:89 | ||||||
| msgid "Error removing repo directory" | msgid "Error removing repo directory" | ||||||
| msgstr "Ошибка при удалении каталога репозитория" | msgstr "Ошибка при удалении каталога репозитория" | ||||||
|  |  | ||||||
| #: repo.go:114 repo.go:195 repo.go:253 repo.go:316 repo.go:389 repo.go:504 | #: repo.go:93 repo.go:160 | ||||||
| #: repo.go:576 |  | ||||||
| msgid "Error saving config" | msgid "Error saving config" | ||||||
| msgstr "Ошибка при сохранении конфигурации" | msgstr "Ошибка при сохранении конфигурации" | ||||||
|  |  | ||||||
| #: repo.go:133 | #: repo.go:112 | ||||||
| msgid "Error removing packages from database" | msgid "Error removing packages from database" | ||||||
| msgstr "Ошибка при удалении пакетов из базы данных" | msgstr "Ошибка при удалении пакетов из базы данных" | ||||||
|  |  | ||||||
| #: repo.go:144 repo.go:595 | #: repo.go:123 repo.go:190 | ||||||
| msgid "Add a new repository" | msgid "Add a new repository" | ||||||
| msgstr "Добавить новый репозиторий" | msgstr "Добавить новый репозиторий" | ||||||
|  |  | ||||||
| #: repo.go:145 repo.go:270 repo.go:345 repo.go:402 | #: repo.go:124 | ||||||
| msgid "<name> <url>" | msgid "<name> <url>" | ||||||
| msgstr "<имя> <url>" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:170 | #: repo.go:149 | ||||||
| msgid "Repo \"%s\" already exists" | msgid "Repo \"%s\" already exists" | ||||||
| msgstr "Репозиторий \"%s\" уже существует" | msgstr "Репозиторий \"%s\" уже существует" | ||||||
|  |  | ||||||
| #: repo.go:206 | #: repo.go:197 | ||||||
| msgid "Set the reference of the repository" |  | ||||||
| msgstr "Установить ссылку на версию репозитория" |  | ||||||
|  |  | ||||||
| #: repo.go:207 |  | ||||||
| msgid "<name> <ref>" |  | ||||||
| msgstr "<имя> <ссылка_на_версию>" |  | ||||||
|  |  | ||||||
| #: repo.go:269 |  | ||||||
| msgid "Set the main url of the repository" |  | ||||||
| msgstr "Установить главный URL репозитория" |  | ||||||
|  |  | ||||||
| #: repo.go:332 |  | ||||||
| msgid "Manage mirrors of repos" |  | ||||||
| msgstr "Управление зеркалами репозитория" |  | ||||||
|  |  | ||||||
| #: repo.go:344 |  | ||||||
| msgid "Add a mirror URL to repository" |  | ||||||
| msgstr "Добавить зеркало репозитория" |  | ||||||
|  |  | ||||||
| #: repo.go:401 |  | ||||||
| msgid "Remove mirror from the repository" |  | ||||||
| msgstr "Удалить зеркало из репозитория" |  | ||||||
|  |  | ||||||
| #: repo.go:420 |  | ||||||
| msgid "Ignore if mirror does not exist" |  | ||||||
| msgstr "Игнорировать, если зеркала не существует" |  | ||||||
|  |  | ||||||
| #: repo.go:425 |  | ||||||
| msgid "Match partial URL (e.g., github.com instead of full URL)" |  | ||||||
| msgstr "Соответствует частичному URL (например, github.com вместо полного URL)" |  | ||||||
|  |  | ||||||
| #: repo.go:490 |  | ||||||
| msgid "No mirrors containing \"%s\" found in repo \"%s\"" |  | ||||||
| msgstr "В репозитории \"%s\" не найдено зеркал, содержащих \"%s\"" |  | ||||||
|  |  | ||||||
| #: repo.go:492 |  | ||||||
| msgid "URL \"%s\" does not exist in repo \"%s\"" |  | ||||||
| msgstr "URL \"%s\" не существует в репозитории \"%s\"" |  | ||||||
|  |  | ||||||
| #: repo.go:508 repo.go:580 |  | ||||||
| msgid "Removed %d mirrors from repo \"%s\"\n" |  | ||||||
| msgstr "Удалены зеркала %d из репозитория \"%s\"\n" |  | ||||||
|  |  | ||||||
| #: repo.go:520 |  | ||||||
| msgid "Remove all mirrors from the repository" |  | ||||||
| msgstr "Удалить все зеркала из репозитория" |  | ||||||
|  |  | ||||||
| #: repo.go:602 |  | ||||||
| msgid "Name of the new repo" | msgid "Name of the new repo" | ||||||
| msgstr "Название нового репозитория" | msgstr "Название нового репозитория" | ||||||
|  |  | ||||||
| #: repo.go:608 | #: repo.go:203 | ||||||
| msgid "URL of the new repo" | msgid "URL of the new repo" | ||||||
| msgstr "URL-адрес нового репозитория" | msgstr "URL-адрес нового репозитория" | ||||||
|  |  | ||||||
| #: repo.go:632 | #: repo.go:227 | ||||||
| msgid "Name of the repo to be deleted" | msgid "Name of the repo to be deleted" | ||||||
| msgstr "Название репозитория  удалён" | msgstr "Название репозитория  удалён" | ||||||
|  |  | ||||||
| @@ -662,25 +551,18 @@ msgstr "Иcкать по provides" | |||||||
| msgid "Error while executing search" | msgid "Error while executing search" | ||||||
| msgstr "Ошибка при выполнении поиска" | msgstr "Ошибка при выполнении поиска" | ||||||
|  |  | ||||||
| #: upgrade.go:48 | #: upgrade.go:47 | ||||||
| msgid "Upgrade all installed packages" | msgid "Upgrade all installed packages" | ||||||
| msgstr "Обновить все установленные пакеты" | msgstr "Обновить все установленные пакеты" | ||||||
|  |  | ||||||
| #: upgrade.go:106 upgrade.go:123 | #: upgrade.go:105 upgrade.go:122 | ||||||
| msgid "Error checking for updates" | msgid "Error checking for updates" | ||||||
| msgstr "Ошибка при проверке обновлений" | msgstr "Ошибка при проверке обновлений" | ||||||
|  |  | ||||||
| #: upgrade.go:126 | #: upgrade.go:125 | ||||||
| msgid "There is nothing to do." | msgid "There is nothing to do." | ||||||
| msgstr "Здесь нечего делать." | msgstr "Здесь нечего делать." | ||||||
|  |  | ||||||
| #, fuzzy |  | ||||||
| #~ msgid "Failed to clear contents of cache directory" |  | ||||||
| #~ msgstr "Не удалось создать каталог кэша репозитория" |  | ||||||
|  |  | ||||||
| #~ msgid "Error iterating over packages" |  | ||||||
| #~ msgstr "Ошибка при переборе пакетов" |  | ||||||
|  |  | ||||||
| #~ msgid "Error pulling repos" | #~ msgid "Error pulling repos" | ||||||
| #~ msgstr "Ошибка при извлечении репозиториев" | #~ msgstr "Ошибка при извлечении репозиториев" | ||||||
|  |  | ||||||
| @@ -696,6 +578,9 @@ msgstr "Здесь нечего делать." | |||||||
| #~ msgid "Unable to create config directory" | #~ msgid "Unable to create config directory" | ||||||
| #~ msgstr "Не удалось создать каталог конфигурации ALR" | #~ msgstr "Не удалось создать каталог конфигурации ALR" | ||||||
|  |  | ||||||
|  | #~ msgid "Unable to create repo cache directory" | ||||||
|  | #~ msgstr "Не удалось создать каталог кэша репозитория" | ||||||
|  |  | ||||||
| #~ msgid "Unable to create package cache directory" | #~ msgid "Unable to create package cache directory" | ||||||
| #~ msgstr "Не удалось создать каталог кэша пакетов" | #~ msgstr "Не удалось создать каталог кэша пакетов" | ||||||
|  |  | ||||||
| @@ -715,6 +600,9 @@ msgstr "Здесь нечего делать." | |||||||
| #~ msgid "Installing build dependencies" | #~ msgid "Installing build dependencies" | ||||||
| #~ msgstr "Установка зависимостей сборки" | #~ msgstr "Установка зависимостей сборки" | ||||||
|  |  | ||||||
|  | #~ msgid "Would you like to remove the build dependencies?" | ||||||
|  | #~ msgstr "Хотели бы вы удалить зависимости сборки?" | ||||||
|  |  | ||||||
| #~ msgid "Error installing native packages" | #~ msgid "Error installing native packages" | ||||||
| #~ msgstr "Ошибка при установке нативных пакетов" | #~ msgstr "Ошибка при установке нативных пакетов" | ||||||
|  |  | ||||||
| @@ -731,6 +619,9 @@ msgstr "Здесь нечего делать." | |||||||
| #~ msgid "Unable to detect user config directory" | #~ msgid "Unable to detect user config directory" | ||||||
| #~ msgstr "Не удалось обнаружить каталог конфигурации пользователя" | #~ msgstr "Не удалось обнаружить каталог конфигурации пользователя" | ||||||
|  |  | ||||||
|  | #~ msgid "Unable to create ALR config file" | ||||||
|  | #~ msgstr "Не удалось создать конфигурационный файл ALR" | ||||||
|  |  | ||||||
| #~ msgid "Error encoding default configuration" | #~ msgid "Error encoding default configuration" | ||||||
| #~ msgstr "Ошибка кодирования конфигурации по умолчанию" | #~ msgstr "Ошибка кодирования конфигурации по умолчанию" | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										86
									
								
								internal/types/build.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								internal/types/build.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | // 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 types | ||||||
|  |  | ||||||
|  | type BuildOpts struct { | ||||||
|  | 	Clean       bool | ||||||
|  | 	Interactive bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type BuildVarsPre struct { | ||||||
|  | 	Version          string   `sh:"version,required"` | ||||||
|  | 	Release          int      `sh:"release,required"` | ||||||
|  | 	Epoch            uint     `sh:"epoch"` | ||||||
|  | 	Summary          string   `sh:"summary"` | ||||||
|  | 	Description      string   `sh:"desc"` | ||||||
|  | 	Group            string   `sh:"group"` | ||||||
|  | 	Homepage         string   `sh:"homepage"` | ||||||
|  | 	Maintainer       string   `sh:"maintainer"` | ||||||
|  | 	Architectures    []string `sh:"architectures"` | ||||||
|  | 	Licenses         []string `sh:"license"` | ||||||
|  | 	Provides         []string `sh:"provides"` | ||||||
|  | 	Conflicts        []string `sh:"conflicts"` | ||||||
|  | 	Depends          []string `sh:"deps"` | ||||||
|  | 	BuildDepends     []string `sh:"build_deps"` | ||||||
|  | 	OptDepends       []string `sh:"opt_deps"` | ||||||
|  | 	Replaces         []string `sh:"replaces"` | ||||||
|  | 	Sources          []string `sh:"sources"` | ||||||
|  | 	Checksums        []string `sh:"checksums"` | ||||||
|  | 	Backup           []string `sh:"backup"` | ||||||
|  | 	Scripts          Scripts  `sh:"scripts"` | ||||||
|  | 	AutoReq          []string `sh:"auto_req"` | ||||||
|  | 	AutoProv         []string `sh:"auto_prov"` | ||||||
|  | 	AutoReqSkipList  []string `sh:"auto_req_skiplist"` | ||||||
|  | 	AutoProvSkipList []string `sh:"auto_prov_skiplist"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (bv *BuildVarsPre) ToBuildVars() BuildVars { | ||||||
|  | 	return BuildVars{ | ||||||
|  | 		Name:         "", | ||||||
|  | 		Base:         "", | ||||||
|  | 		BuildVarsPre: *bv, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // BuildVars represents the script variables required | ||||||
|  | // to build a package | ||||||
|  | type BuildVars struct { | ||||||
|  | 	Name string `sh:"name,required"` | ||||||
|  | 	Base string | ||||||
|  | 	BuildVarsPre | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Scripts struct { | ||||||
|  | 	PreInstall  string `sh:"preinstall"` | ||||||
|  | 	PostInstall string `sh:"postinstall"` | ||||||
|  | 	PreRemove   string `sh:"preremove"` | ||||||
|  | 	PostRemove  string `sh:"postremove"` | ||||||
|  | 	PreUpgrade  string `sh:"preupgrade"` | ||||||
|  | 	PostUpgrade string `sh:"postupgrade"` | ||||||
|  | 	PreTrans    string `sh:"pretrans"` | ||||||
|  | 	PostTrans   string `sh:"posttrans"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Directories struct { | ||||||
|  | 	BaseDir   string | ||||||
|  | 	SrcDir    string | ||||||
|  | 	PkgDir    string | ||||||
|  | 	ScriptDir string | ||||||
|  | } | ||||||
| @@ -19,25 +19,20 @@ | |||||||
| 
 | 
 | ||||||
| package types | package types | ||||||
| 
 | 
 | ||||||
| type BuildOpts struct { | // Config represents the ALR configuration file | ||||||
| 	Clean       bool | type Config struct { | ||||||
| 	Interactive bool | 	RootCmd          string   `toml:"rootCmd" env:"ALR_ROOT_CMD"` | ||||||
|  | 	UseRootCmd       bool     `toml:"useRootCmd"` | ||||||
|  | 	PagerStyle       string   `toml:"pagerStyle" env:"ALR_PAGER_STYLE"` | ||||||
|  | 	IgnorePkgUpdates []string `toml:"ignorePkgUpdates"` | ||||||
|  | 	Repos            []Repo   `toml:"repo"` | ||||||
|  | 	AutoPull         bool     `toml:"autoPull" env:"ALR_AUTOPULL"` | ||||||
|  | 	LogLevel         string   `toml:"logLevel" env:"ALR_LOG_LEVEL"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Scripts struct { | // Repo represents a ALR repo within a configuration file | ||||||
| 	PreInstall  string `sh:"preinstall"` | type Repo struct { | ||||||
| 	PostInstall string `sh:"postinstall"` | 	Name string `toml:"name"` | ||||||
| 	PreRemove   string `sh:"preremove"` | 	URL  string `toml:"url"` | ||||||
| 	PostRemove  string `sh:"postremove"` | 	Ref  string `toml:"ref"` | ||||||
| 	PreUpgrade  string `sh:"preupgrade"` |  | ||||||
| 	PostUpgrade string `sh:"postupgrade"` |  | ||||||
| 	PreTrans    string `sh:"pretrans"` |  | ||||||
| 	PostTrans   string `sh:"posttrans"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Directories struct { |  | ||||||
| 	BaseDir   string |  | ||||||
| 	SrcDir    string |  | ||||||
| 	PkgDir    string |  | ||||||
| 	ScriptDir string |  | ||||||
| } | } | ||||||
| @@ -22,9 +22,6 @@ package types | |||||||
| // RepoConfig represents a ALR repo's alr-repo.toml file. | // RepoConfig represents a ALR repo's alr-repo.toml file. | ||||||
| type RepoConfig struct { | type RepoConfig struct { | ||||||
| 	Repo struct { | 	Repo struct { | ||||||
| 		MinVersion string   `toml:"minVersion"` | 		MinVersion string `toml:"minVersion"` | ||||||
| 		URL        string   `toml:"url"` |  | ||||||
| 		Ref        string   `toml:"ref"` |  | ||||||
| 		Mirrors    []string `toml:"mirrors"` |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -17,9 +17,12 @@ | |||||||
| package utils | package utils | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"os/user" | 	"os/user" | ||||||
|  | 	"strconv" | ||||||
|  | 	"syscall" | ||||||
|  |  | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| @@ -29,23 +32,115 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // IsNotRoot проверяет, что текущий пользователь не является root | func GetUidGidAlrUserString() (string, string, error) { | ||||||
|  | 	u, err := user.Lookup("alr") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return u.Uid, u.Gid, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetUidGidAlrUser() (int, int, error) { | ||||||
|  | 	strUid, strGid, err := GetUidGidAlrUserString() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, 0, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	uid, err := strconv.Atoi(strUid) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, 0, err | ||||||
|  | 	} | ||||||
|  | 	gid, err := strconv.Atoi(strGid) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, 0, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return uid, gid, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func DropCapsToAlrUser() error { | ||||||
|  | 	uid, gid, err := GetUidGidAlrUser() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = syscall.Setgid(gid) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = syscall.Setuid(uid) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return EnsureIsAlrUser() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ExitIfCantDropGidToAlr() cli.ExitCoder { | ||||||
|  | 	_, gid, err := GetUidGidAlrUser() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return cliutils.FormatCliExit("cannot get gid alr", err) | ||||||
|  | 	} | ||||||
|  | 	err = syscall.Setgid(gid) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return cliutils.FormatCliExit("cannot get setgid alr", err) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ExitIfCantDropCapsToAlrUser attempts to drop capabilities to the already | ||||||
|  | // running user. Returns a cli.ExitCoder with an error if the operation fails. | ||||||
|  | // See also [ExitIfCantDropCapsToAlrUserNoPrivs] for a version that also applies | ||||||
|  | // no-new-privs. | ||||||
|  | func ExitIfCantDropCapsToAlrUser() cli.ExitCoder { | ||||||
|  | 	err := DropCapsToAlrUser() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return cliutils.FormatCliExit(gotext.Get("Error on dropping capabilities"), err) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ExitIfCantSetNoNewPrivs() cli.ExitCoder { | ||||||
|  | 	if err := NoNewPrivs(); err != nil { | ||||||
|  | 		return cliutils.FormatCliExit("error on NoNewPrivs", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ExitIfCantDropCapsToAlrUserNoPrivs combines [ExitIfCantDropCapsToAlrUser] with [ExitIfCantSetNoNewPrivs] | ||||||
|  | func ExitIfCantDropCapsToAlrUserNoPrivs() cli.ExitCoder { | ||||||
|  | 	if err := ExitIfCantDropCapsToAlrUser(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := ExitIfCantSetNoNewPrivs(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func IsNotRoot() bool { | func IsNotRoot() bool { | ||||||
| 	return os.Getuid() != 0 | 	return os.Getuid() != 0 | ||||||
| } | } | ||||||
|  |  | ||||||
| // EnuseIsPrivilegedGroupMember проверяет, что пользователь является членом привилегированной группы (wheel) | func EnsureIsAlrUser() error { | ||||||
|  | 	uid, gid, err := GetUidGidAlrUser() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	newUid := syscall.Getuid() | ||||||
|  | 	if newUid != uid { | ||||||
|  | 		return errors.New("new uid don't matches requested") | ||||||
|  | 	} | ||||||
|  | 	newGid := syscall.Getgid() | ||||||
|  | 	if newGid != gid { | ||||||
|  | 		return errors.New("new gid don't matches requested") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func EnuseIsPrivilegedGroupMember() error { | func EnuseIsPrivilegedGroupMember() error { | ||||||
| 	// В CI пропускаем проверку группы wheel |  | ||||||
| 	if os.Getenv("CI") == "true" { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	// Если пользователь root, пропускаем проверку |  | ||||||
| 	if os.Geteuid() == 0 { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	currentUser, err := user.Current() | 	currentUser, err := user.Current() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -69,6 +164,26 @@ func EnuseIsPrivilegedGroupMember() error { | |||||||
| 	return cliutils.FormatCliExit(gotext.Get("You need to be a %s member to perform this action", constants.PrivilegedGroup), nil) | 	return cliutils.FormatCliExit(gotext.Get("You need to be a %s member to perform this action", constants.PrivilegedGroup), nil) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func EscalateToRootGid() error { | ||||||
|  | 	return syscall.Setgid(0) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func EscalateToRootUid() error { | ||||||
|  | 	return syscall.Setuid(0) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func EscalateToRoot() error { | ||||||
|  | 	err := EscalateToRootUid() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = EscalateToRootGid() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func RootNeededAction(f cli.ActionFunc) cli.ActionFunc { | func RootNeededAction(f cli.ActionFunc) cli.ActionFunc { | ||||||
| 	return func(ctx *cli.Context) error { | 	return func(ctx *cli.Context) error { | ||||||
| 		deps, err := appbuilder. | 		deps, err := appbuilder. | ||||||
|   | |||||||
| @@ -16,78 +16,8 @@ | |||||||
|  |  | ||||||
| package utils | package utils | ||||||
|  |  | ||||||
| import ( | import "golang.org/x/sys/unix" | ||||||
| 	"os" |  | ||||||
| 	"os/exec" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"golang.org/x/sys/unix" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func NoNewPrivs() error { | func NoNewPrivs() error { | ||||||
| 	return unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) | 	return unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) | ||||||
| } | } | ||||||
|  |  | ||||||
| // EnsureTempDirWithRootOwner создает каталог в /tmp/alr с правами для группы wheel |  | ||||||
| // Все каталоги в /tmp/alr принадлежат root:wheel с правами 775 |  | ||||||
| // Для других каталогов использует стандартные права |  | ||||||
| func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error { |  | ||||||
| 	if strings.HasPrefix(path, "/tmp/alr") { |  | ||||||
| 		// Сначала создаем директорию обычным способом |  | ||||||
| 		err := os.MkdirAll(path, mode) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		// В CI или если мы уже root, не нужно использовать sudo |  | ||||||
| 		isRoot := os.Geteuid() == 0 |  | ||||||
| 		isCI := os.Getenv("CI") == "true" |  | ||||||
| 		 |  | ||||||
| 		// В CI создаем директории с обычными правами |  | ||||||
| 		if isCI { |  | ||||||
| 			// В CI не используем группу wheel и не меняем права |  | ||||||
| 			// Устанавливаем базовые права 777 для временных каталогов |  | ||||||
| 			chmodCmd := exec.Command("chmod", "777", path) |  | ||||||
| 			chmodCmd.Run() // Игнорируем ошибки |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		// Для обычной работы устанавливаем права и группу wheel |  | ||||||
| 		permissions := "2775" |  | ||||||
| 		group := "wheel" |  | ||||||
| 		 |  | ||||||
| 		var chmodCmd, chownCmd *exec.Cmd |  | ||||||
| 		if isRoot { |  | ||||||
| 			// Выполняем команды напрямую без sudo |  | ||||||
| 			chmodCmd = exec.Command("chmod", permissions, path) |  | ||||||
| 			chownCmd = exec.Command("chown", "root:"+group, path) |  | ||||||
| 		} else { |  | ||||||
| 			// Используем sudo для обычных пользователей |  | ||||||
| 			chmodCmd = exec.Command("sudo", "chmod", permissions, path) |  | ||||||
| 			chownCmd = exec.Command("sudo", "chown", "root:"+group, path) |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		// Устанавливаем права с setgid битом |  | ||||||
| 		err = chmodCmd.Run() |  | ||||||
| 		if err != nil { |  | ||||||
| 			// Для root игнорируем ошибки, если группа wheel не существует |  | ||||||
| 			if !isRoot { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		// Устанавливаем владельца root:wheel |  | ||||||
| 		err = chownCmd.Run() |  | ||||||
| 		if err != nil { |  | ||||||
| 			// Для root игнорируем ошибки, если группа wheel не существует |  | ||||||
| 			if !isRoot { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	// Для остальных каталогов обычное создание |  | ||||||
| 	return os.MkdirAll(path, mode) |  | ||||||
| } |  | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user