Compare commits
	
		
			270 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 107075e8ef | |||
| 41e3d8119f | |||
| cf804ec66b | |||
| 6773d51caf | |||
| 4a616f2137 | |||
| 9efebbc02a | |||
| ef41d682a1 | |||
| 42f0d5e575 | |||
| 7b9404a058 | |||
| 18e8dc3fbf | |||
| 9c0af83a20 | |||
| 4bd20d84ef | |||
| 8dea5e1e7f | |||
| 86a982478e | |||
| 8bc82cb95c | |||
| 9783ce37de | |||
| b852688ab0 | |||
| 2ff5e6f7b6 | |||
| c9639b7073 | |||
| c1847e1191 | |||
| f2b0f57c12 | |||
| 59cc41e94c | |||
| 75ece6dfcc | |||
| 6af712f1d5 | |||
| bad225c6b1 | |||
| 4b3bf44aaa | |||
| 67b3c40430 | |||
| 4948e6b8fc | |||
| 292125a8ff | |||
| 77055aa2cb | |||
| 737bf68f95 | |||
| 1089e8a3f3 | |||
| aa42ab0607 | |||
| 51fa7ca6fb | |||
| ab41700004 | |||
| 7cb1bc9548 | |||
| 07187da423 | |||
| 802fe2b0b2 | |||
| aa08c04e0c | |||
| f42be105ad | |||
| 1cc408ad7d | |||
| 4899e203bb | |||
| 67a6cb31de | |||
| 5e24940ef8 | |||
| a600feb083 | |||
| 7060e4f551 | |||
| d77ca4c384 | |||
| 6355f25089 | |||
| a83561b6a5 | |||
| 4b06809a39 | |||
| 401c41160c | |||
| 5e1eeabd04 | |||
| db19133254 | |||
| e8202060d8 | |||
| c4a92c67d4 | |||
| 85878f69d3 | |||
| 6bccce1db4 | |||
| b5474b1eb4 | |||
| 51fdea781b | |||
| 4c1f2ea90f | |||
| 7fa7f8ba82 | |||
| 25d001c1c9 | |||
| f86b3003b1 | |||
| bd79dcf401 | |||
| d1fe02fa57 | |||
| 1ca7801fba | |||
| 661d79ce24 | |||
| bece64c132 | |||
| 6d29b98cf7 | |||
| d286041864 | |||
| 392a522723 | |||
| e259184a89 | |||
| 65ab4de561 | |||
| 1cdab8dfed | |||
| 237e2c338d | |||
| 703ab8e8c4 | |||
| 06fcab4ce7 | |||
| 7741c7368b | |||
| 69f4af0a4d | |||
| bcf627f176 | |||
| 6ec95e4bd9 | |||
| 578da7ff52 | |||
| c51caf5c52 | |||
| 09dba577c6 | |||
| ca82bf3024 | |||
| c0023db6cd | |||
| 152e5077ec | |||
| 15ba8700e8 | |||
| a8aefc0524 | |||
| 9540030579 | |||
| 4f9d4260b8 | |||
| 38b5e6f581 | |||
| 408bd12302 | |||
| fb83d544de | |||
| 2cb963d4b2 | |||
| e74d74cdf6 | |||
| 5b3d53d253 | |||
| 36e704f735 | |||
| 07356d5e55 | |||
| 52bd6aca93 | |||
| 2f1770b43b | |||
| 9d5b5b51ff | |||
| c88478a450 | |||
| 3e61fec67c | |||
| 6f484a1169 | |||
| dddcb9b7b0 | |||
| b03d94e48b | |||
| f92bd7089a | |||
| eb2356458c | |||
| 131f455eff | |||
| 1e52d30f4c | |||
| 40ec0ac6e1 | |||
| 443e481561 | |||
| c892310f69 | |||
| 750513b119 | |||
| ce1836b646 | |||
| 56b9f3211c | |||
| fae63e28f9 | |||
| c632ddb354 | |||
| 76234bf00d | |||
| f8c510ab9f | |||
| 849a08a791 | |||
| 952dd26f5f | |||
| 080c9f42ff | |||
| 3c3ee286ce | |||
| d0d8930491 | |||
| 93508647e0 | |||
| 6135e55f92 | |||
| 2b7c2bbbb3 | |||
| afe35f407e | |||
| c51d1d9202 | |||
| b46dd41ada | |||
| f623cba5c0 | |||
| e552663442 | |||
| 7bbceb76c9 | |||
| bd6e3bbe27 | |||
| 0d917190ab | |||
| 83b8f3b047 | |||
| 3483cf57f8 | |||
| efa4f59403 | |||
| c59ed6d505 | |||
| 2bbc308810 | |||
| 5ca34a572a | |||
| 67e63d1831 | |||
| 0bfe88beed | |||
| 3ce9f0db35 | |||
| 469a05105a | |||
| 70aca61750 | |||
| 4b35f5e4e6 | |||
| 7770675a8d | |||
| bd79d56776 | |||
| fff8b777fe | |||
| 6bee268ea9 | |||
| 4b53e819d8 | |||
| 6c0e8aeb3f | |||
| cbc6b9f452 | |||
| c705c25613 | |||
| 8f4b021a93 | |||
| 5e7d4033e4 | |||
| 4ac2432770 | |||
| 3c37310f0d | |||
| d300ab197b | |||
| eb2cc3c1e6 | |||
| 7a3acfe5c1 | |||
| 9cf8af08ab | |||
| 86940e8962 | |||
| db244204c7 | |||
| 9cb0a5e9ad | |||
| 1a57ccdb83 | |||
| 615cd83fb7 | |||
| 27e2f54653 | |||
| af57165c89 | |||
| 3770c82240 | |||
| 2dff463303 | |||
| 9085e38454 | |||
| a7d016abc9 | |||
| 4a5cca2d0f | |||
| 71000fd3cd | |||
| 71968bbe13 | |||
| 29c1a31066 | |||
| 8f94b61a0e | |||
| ae8e2d2807 | |||
| 0fa288b8a2 | |||
| dcac0b9ee5 | |||
| 4e6e1f524a | |||
| 47a9b9a96c | |||
| 9bb14312bd | |||
| 88b8d2fbf3 | |||
| 04523775f1 | |||
| adc4a42800 | |||
| 81651af20d | |||
| f04ebbaf14 | |||
| be1a137eab | |||
| 719a5b7fe7 | |||
| e05bb07f23 | |||
| ec053f7e6a | |||
| ad1696d507 | |||
| a57602a278 | |||
| 606cd5473a | |||
| d2bcb4e345 | |||
| 1fcb88976c | |||
| 55feea9b25 | |||
| 0d1db212e1 | |||
| 99ec48c4c6 | |||
| d201aae6e0 | |||
| 4463a32ae7 | |||
| 52fd4a5a00 | |||
| c437346957 | |||
| bf1fc0d878 | |||
| c3f879b379 | |||
| aa90dfa983 | |||
| bba1ed52c5 | |||
| dc1fac29d5 | |||
| 99857efb01 | |||
| 19bb87981c | |||
| 1c78adcca1 | |||
| a98bd44305 | |||
| 3deb6c9455 | |||
| 981f49587b | |||
| 35656d63a1 | |||
| 6410f7547b | |||
| 53e783df31 | |||
| fcc9ef5474 | |||
| f6ba4a1c26 | |||
| b5bf6ab61d | |||
| 18e90e4afc | |||
| a09863dfcb | |||
| fd643ea6cd | |||
| 309ecf784f | |||
| 30f95a4cbf | |||
| b9bf908007 | |||
| a6076b1253 | |||
| ac35b4d71d | |||
| 945f920654 | |||
| 84ac2377fb | |||
| de1db25202 | |||
| 2d6504b329 | |||
| 4ca557402a | |||
| e497d41030 | |||
| d46414a67c | |||
| 29e2f85eeb | |||
| c9c872abbc | |||
| fb93864d09 | |||
| 9fcd618a83 | |||
| 1fb9c6b574 | |||
| fb5c875713 | |||
| 3f428ab7b5 | |||
| 5b7af1f6b5 | |||
| 3224d7c6e4 | |||
| e1829c4824 | |||
| 12d83f2015 | |||
| 6bc6bfdcd9 | |||
| eeb25c239b | |||
| 91937a1fc5 | |||
| e827fb8049 | |||
| a13acc5ed0 | |||
| 52d3ab7791 | |||
| a345a24b95 | |||
| 5d1d3d7c45 | |||
| a711edbcc0 | |||
| d5636e8094 | |||
| 5d17875813 | |||
| 41eec2fc98 | |||
| 1273aeae39 | |||
| 49785d4dc8 | |||
| 1890311d11 | |||
| eb1c1a1d8c | |||
| c105cf2cbb | |||
| 94048184c1 | |||
| 3f1c1b6795 | 
							
								
								
									
										57
									
								
								.gitea/workflows/e2e-tests.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								.gitea/workflows/e2e-tests.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | # ALR - Any Linux Repository | ||||||
|  | # Copyright (C) 2025 The ALR Authors | ||||||
|  | # | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU General Public License | ||||||
|  | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | name: E2E | ||||||
|  |  | ||||||
|  | # on: | ||||||
|  | #   push: | ||||||
|  | #     branches: [ main ] | ||||||
|  | #   pull_request: | ||||||
|  | on: | ||||||
|  |   workflow_dispatch: | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   tests: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |  | ||||||
|  |     container: | ||||||
|  |       image: altlinux.space/maks1ms/actions-container-runner:latest | ||||||
|  |  | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout | ||||||
|  |         uses: https://github.com/actions/checkout@v4 | ||||||
|  |         with: | ||||||
|  |           fetch-depth: 0 | ||||||
|  |        | ||||||
|  |       - name: Set up Go | ||||||
|  |         uses: https://github.com/actions/setup-go@v5 | ||||||
|  |         with: | ||||||
|  |           go-version: '1.24' | ||||||
|  |           cache: false | ||||||
|  |  | ||||||
|  |       # - name: Cache Podman images | ||||||
|  |       #   uses: actions/cache@v4 | ||||||
|  |       #   with: | ||||||
|  |       #     path: | | ||||||
|  |       #       ~/.local/share/containers/storage | ||||||
|  |       #       /var/lib/containers/storage | ||||||
|  |       #     key: ${{ runner.os }}-primes             | ||||||
|  |  | ||||||
|  |       - name: Run E2E tests | ||||||
|  |         env: | ||||||
|  |           IGNORE_ROOT_CHECK: 1 | ||||||
|  |         run: | | ||||||
|  |           make e2e-test | ||||||
							
								
								
									
										51
									
								
								.gitea/workflows/pre-commit.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								.gitea/workflows/pre-commit.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | # ALR - Any Linux Repository | ||||||
|  | # Copyright (C) 2025 The ALR Authors | ||||||
|  | # | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU General Public License | ||||||
|  | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | name: Pre-commit | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: [ master ] | ||||||
|  |   pull_request: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   pre-commit: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     container: | ||||||
|  |       image: docker.gitea.com/runner-images:ubuntu-latest | ||||||
|  |  | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout | ||||||
|  |         uses: https://github.com/actions/checkout@v4 | ||||||
|  |  | ||||||
|  |       - name: Set up Go | ||||||
|  |         uses: https://github.com/actions/setup-go@v5 | ||||||
|  |         with: | ||||||
|  |           go-version: '1.24' | ||||||
|  |        | ||||||
|  |       - name: Set up Python for pre-commit | ||||||
|  |         uses: https://github.com/actions/setup-python@v5 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.12' | ||||||
|  |        | ||||||
|  |       - name: Install deps | ||||||
|  |         run: apt-get update && apt-get install -y gettext bc | ||||||
|  |  | ||||||
|  |       - run: pip install pre-commit | ||||||
|  |       - run: pre-commit install | ||||||
|  |       - run: pre-commit run --all-files | ||||||
							
								
								
									
										134
									
								
								.gitea/workflows/release.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								.gitea/workflows/release.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | |||||||
|  | # ALR - Any Linux Repository | ||||||
|  | # Copyright (C) 2025 The ALR Authors | ||||||
|  | # | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU General Public License | ||||||
|  | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | name: Create Release | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     tags: | ||||||
|  |       - 'v[0-9]+.[0-9]+.[0-9]+' | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   changelog: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout this repository | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |         with: | ||||||
|  |           fetch-depth: 0 | ||||||
|  |  | ||||||
|  |       - name: Set up Go | ||||||
|  |         uses: actions/setup-go@v5 | ||||||
|  |         with: | ||||||
|  |           go-version: '1.24' | ||||||
|  |  | ||||||
|  |       - name: Get Changes between Tags | ||||||
|  |         id: changes | ||||||
|  |         uses: simbo/changes-between-tags-action@v1 | ||||||
|  |  | ||||||
|  |       - name: Set version | ||||||
|  |         run: | | ||||||
|  |           version=$(echo "${GITHUB_REF##*/}" | sed 's/^v//') | ||||||
|  |           echo "Version - $version" | ||||||
|  |           echo "VERSION=$version" >> $GITHUB_ENV | ||||||
|  |  | ||||||
|  |       - name: Prepare for install | ||||||
|  |         run: | | ||||||
|  |           apt-get update | ||||||
|  |  | ||||||
|  |       - name: Build alr | ||||||
|  |         env: | ||||||
|  |           IGNORE_ROOT_CHECK: 1 | ||||||
|  |         run: | | ||||||
|  |           make build | ||||||
|  |  | ||||||
|  |       - name: Create tar.gz | ||||||
|  |         run: | | ||||||
|  |           mkdir -p ./out/completion | ||||||
|  |           cp alr ./out | ||||||
|  |           cp scripts/completion/bash ./out/completion/alr | ||||||
|  |           cp scripts/completion/zsh ./out/completion/_alr | ||||||
|  |  | ||||||
|  |           ( cd out && tar -czvf ../alr-${{ env.VERSION }}-linux-x86_64.tar.gz * ) | ||||||
|  |  | ||||||
|  |       - name: Release | ||||||
|  |         uses: akkuman/gitea-release-action@v1 | ||||||
|  |         with: | ||||||
|  |           body: ${{ steps.changes.outputs.changes }} | ||||||
|  |           files: |- | ||||||
|  |             alr-${{ env.VERSION }}-linux-x86_64.tar.gz | ||||||
|  |  | ||||||
|  |       - name: Checkout alr-default repository | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |         with: | ||||||
|  |           repository: Plemya-x/alr-default | ||||||
|  |           token: ${{ secrets.GITEAPUBLIC }} | ||||||
|  |           path: alr-default | ||||||
|  |  | ||||||
|  |       - name: Calculate checksum | ||||||
|  |         run: | | ||||||
|  |           # Вычисляем SHA256 контрольную сумму архива | ||||||
|  |           CHECKSUM=$(sha256sum alr-${{ env.VERSION }}-linux-x86_64.tar.gz | awk '{print $1}') | ||||||
|  |           echo "Archive checksum: $CHECKSUM" | ||||||
|  |           echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV | ||||||
|  |  | ||||||
|  |       - name: Update version and checksum in alr-bin | ||||||
|  |         run: | | ||||||
|  |           # Обновляем версию | ||||||
|  |           sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh | ||||||
|  |           sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh | ||||||
|  |  | ||||||
|  |           # Обновляем контрольную сумму | ||||||
|  |           sed -i "s/checksums=('[^']*')/checksums=('${{ env.CHECKSUM }}')/g" alr-default/alr-bin/alr.sh | ||||||
|  |  | ||||||
|  |       - name: Commit and push changes to alr-default | ||||||
|  |         run: | | ||||||
|  |           cd alr-default | ||||||
|  |           git config user.name "gitea" | ||||||
|  |           git config user.email "admin@plemya-x.ru" | ||||||
|  |           git add alr-bin/alr.sh | ||||||
|  |           git commit -m "Обновление alr-bin до версии ${{ env.VERSION }}" | ||||||
|  |           git push | ||||||
|  |  | ||||||
|  |       - name: Install alr | ||||||
|  |         env: | ||||||
|  |           CREATE_SYSTEM_RESOURCES: 0 | ||||||
|  |         run: | | ||||||
|  |           make install | ||||||
|  |  | ||||||
|  |       - name: Prepare directories for ALR | ||||||
|  |         run: | | ||||||
|  |           # Создаём необходимые директории для работы alr build | ||||||
|  |           mkdir -p /tmp/alr/dl /tmp/alr/pkgs /var/cache/alr | ||||||
|  |           chmod -R 777 /tmp/alr | ||||||
|  |           chmod -R 755 /var/cache/alr | ||||||
|  |  | ||||||
|  |       - name: Build packages | ||||||
|  |         run: | | ||||||
|  |           SCRIPT_PATH=alr-default/alr-bin/alr.sh | ||||||
|  |           ALR_DISTRO=altlinux ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH" | ||||||
|  |           ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH" | ||||||
|  |           ALR_PKG_FORMAT=deb alr build -s "$SCRIPT_PATH" | ||||||
|  |           ALR_PKG_FORMAT=archlinux alr build -s "$SCRIPT_PATH" | ||||||
|  |  | ||||||
|  |       - name: Upload assets | ||||||
|  |         uses: akkuman/gitea-release-action@v1 | ||||||
|  |         with: | ||||||
|  |           body: ${{ steps.changes.outputs.changes }} | ||||||
|  |           files: |- | ||||||
|  |             alr-bin*.deb | ||||||
|  |             alr-bin*.rpm | ||||||
|  |             alr-bin*.pkg.tar.zst | ||||||
							
								
								
									
										13
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -3,5 +3,14 @@ | |||||||
| /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/ | ||||||
|  |  | ||||||
|  | *.out | ||||||
|  |  | ||||||
|  | e2e-tests/alr | ||||||
|  | CLAUDE.md | ||||||
|  | commit_msg.txt | ||||||
|  | /scripts/.claude/settings.local.json | ||||||
|  | /ALR | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | # ALR - Any Linux Repository | ||||||
|  | # Copyright (C) 2025 The ALR Authors | ||||||
|  | # | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU General Public License | ||||||
|  | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | run: | ||||||
|  |   timeout: 5m | ||||||
|  |  | ||||||
|  | linters-settings: | ||||||
|  |   goimports: | ||||||
|  |     local-prefixes: "gitea.plemya-x.ru/Plemya-x/ALR" | ||||||
|  |   gofmt: | ||||||
|  |     simplify: true | ||||||
|  |   gofumpt: | ||||||
|  |     extra-rules: true | ||||||
|  |  | ||||||
|  | linters: | ||||||
|  |   enable: | ||||||
|  |     - gofmt | ||||||
|  |     - gofumpt | ||||||
|  |     - goimports | ||||||
|  |     - gocritic | ||||||
|  |     - govet | ||||||
|  |     - staticcheck | ||||||
|  |     - unused | ||||||
|  |     - errcheck | ||||||
|  |     - typecheck | ||||||
|  |     - wrapcheck | ||||||
|  |  | ||||||
|  | issues: | ||||||
|  |   fix: true | ||||||
|  |   exclude-rules: | ||||||
|  |     - linters: | ||||||
|  |         - wrapcheck | ||||||
|  |       path-except: "internal/repos/find.go" | ||||||
|  |     - path: _test\.go | ||||||
|  |       linters: | ||||||
|  |         - errcheck | ||||||
|  |     # TODO: remove | ||||||
|  |     - linters: | ||||||
|  |         - staticcheck | ||||||
|  |       text: "SA1019: interp.ExecHandler" | ||||||
| @@ -1,99 +0,0 @@ | |||||||
| before: |  | ||||||
|   hooks: |  | ||||||
|     - go mod tidy |  | ||||||
| builds: |  | ||||||
|   - id: alr |  | ||||||
|     env: |  | ||||||
|       - CGO_ENABLED=0 |  | ||||||
|     binary: alr |  | ||||||
|     ldflags: |  | ||||||
|       - -X gitea.plemya-x.ru/xpamych/ALR/src/branch/master/internal/config.Version={{.Version}} |  | ||||||
|     goos: |  | ||||||
|       - linux |  | ||||||
|     goarch: |  | ||||||
|       - amd64 |  | ||||||
|       - 386 |  | ||||||
|       - arm64 |  | ||||||
|       - arm |  | ||||||
|       - riscv64 |  | ||||||
| archives: |  | ||||||
|   - name_template: >- |  | ||||||
|       {{- .ProjectName}}- |  | ||||||
|       {{- .Version}}- |  | ||||||
|       {{- .Os}}- |  | ||||||
|       {{- if .Arch | eq "amd64"}}x86_64 |  | ||||||
|       {{- else if .Arch | eq "386"}}i386 |  | ||||||
|       {{- else if .Arch | eq "arm64"}}aarch64 |  | ||||||
|       {{- else }}{{ .Arch }}{{ end -}} |  | ||||||
|     files: |  | ||||||
|       - scripts/completion/* |  | ||||||
| nfpms: |  | ||||||
|   - id: alr |  | ||||||
|     package_name: linux-user-repository |  | ||||||
|     file_name_template: >- |  | ||||||
|       {{- .PackageName}}- |  | ||||||
|       {{- .Version}}- |  | ||||||
|       {{- .Os}}- |  | ||||||
|       {{- if .Arch | eq "amd64"}}x86_64 |  | ||||||
|       {{- else if .Arch | eq "386"}}i386 |  | ||||||
|       {{- else if .Arch | eq "arm64"}}aarch64 |  | ||||||
|       {{- else }}{{ .Arch }}{{ end -}} |  | ||||||
|     description: "Any Linux Repository" |  | ||||||
|     homepage: 'https://gitea.plemya-x.ru/xpamych/ALR' |  | ||||||
|     maintainer: 'Евгений Храмов <xpamych@yandex.ru>' |  | ||||||
|     license: GPLv3 |  | ||||||
|     formats: |  | ||||||
|       - apk |  | ||||||
|       - deb |  | ||||||
|       - rpm |  | ||||||
|       - archlinux |  | ||||||
|     provides: |  | ||||||
|       - linux-user-repository |  | ||||||
|     conflicts: |  | ||||||
|       - linux-user-repository |  | ||||||
|     recommends: |  | ||||||
|       - aria2 |  | ||||||
|     contents: |  | ||||||
|       - src: scripts/completion/bash |  | ||||||
|         dst: /usr/share/bash-completion/completions/alr |  | ||||||
|       - src: scripts/completion/zsh |  | ||||||
|         dst: /usr/share/zsh/site-functions/_alr |  | ||||||
| aurs: |  | ||||||
|   - name: linux-user-repository-bin |  | ||||||
|     homepage: 'https://gitea.plemya-x.ru/xpamych/ALR' |  | ||||||
|     description: "Any Linux Repository" |  | ||||||
|     maintainers: |  | ||||||
|       - 'Евгений Храмов <xpamych@yandex.ru>' |  | ||||||
|     license: GPLv3 |  | ||||||
|     private_key: '{{ .Env.AUR_KEY }}' |  | ||||||
|     git_url: 'ssh://aur@aur.archlinux.org/linux-user-repository-bin.git' |  | ||||||
|     provides: |  | ||||||
|       - alr |  | ||||||
|     conflicts: |  | ||||||
|       - alr |  | ||||||
|     depends: |  | ||||||
|       - sudo |  | ||||||
|       - pacman |  | ||||||
|     optdepends: |  | ||||||
|       - 'aria2: for downloading torrent sources' |  | ||||||
|     package: |- |  | ||||||
|       # binaries |  | ||||||
|       install -Dm755 ./alr "${pkgdir}/usr/bin/alr" |  | ||||||
|  |  | ||||||
|       # completions |  | ||||||
|       install -Dm755 ./scripts/completion/bash ${pkgdir}/usr/share/bash-completion/completions/alr |  | ||||||
|       install -Dm755 ./scripts/completion/zsh ${pkgdir}/usr/share/zsh/site-functions/_alr |  | ||||||
| release: |  | ||||||
|   gitea: |  | ||||||
|     owner: alr |  | ||||||
|     name: alr |  | ||||||
| gitea_urls: |  | ||||||
|   api: 'https://gitea.elara.ws/api/v1/' |  | ||||||
|   download: 'https://gitea.elara.ws' |  | ||||||
|   skip_tls_verify: false |  | ||||||
| checksum: |  | ||||||
|   name_template: 'checksums.txt' |  | ||||||
| snapshot: |  | ||||||
|   name_template: "{{ incpatch .Version }}-next" |  | ||||||
| changelog: |  | ||||||
|   sort: asc |  | ||||||
							
								
								
									
										43
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | # ALR - Any Linux Repository | ||||||
|  | # Copyright (C) 2025 The ALR Authors | ||||||
|  | # | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU General Public License | ||||||
|  | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | repos: | ||||||
|  |   - repo: local | ||||||
|  |     hooks: | ||||||
|  |       - id: test-coverage | ||||||
|  |         name: Run test coverage | ||||||
|  |         entry: bash scripts/test-coverage-precommit.sh | ||||||
|  |         language: system | ||||||
|  |         pass_filenames: false | ||||||
|  |  | ||||||
|  |       - id: fmt | ||||||
|  |         name: Format code | ||||||
|  |         entry: bash scripts/fmt-precommit.sh | ||||||
|  |         language: system | ||||||
|  |         pass_filenames: false | ||||||
|  |  | ||||||
|  |       - id: update-license | ||||||
|  |         name: Update license | ||||||
|  |         entry: make update-license | ||||||
|  |         language: system | ||||||
|  |         pass_filenames: false | ||||||
|  |  | ||||||
|  |       - id: i18n | ||||||
|  |         name: Update i18n | ||||||
|  |         entry: bash scripts/i18n-precommit.sh | ||||||
|  |         language: system | ||||||
|  |         pass_filenames: false | ||||||
|  |         always_run: true | ||||||
| @@ -1,9 +0,0 @@ | |||||||
| platform: linux/amd64 |  | ||||||
| pipeline: |  | ||||||
|   release: |  | ||||||
|     image: goreleaser/goreleaser |  | ||||||
|     commands: |  | ||||||
|       - goreleaser release |  | ||||||
|     secrets: [ gitea_token, aur_key ] |  | ||||||
|     when: |  | ||||||
|       event: tag |  | ||||||
							
								
								
									
										68
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,38 +1,61 @@ | |||||||
| NAME := alr | NAME := alr | ||||||
| GIT_VERSION = $(shell git describe --tags ) | GIT_VERSION ?= $(shell git describe --tags ) | ||||||
|  | IGNORE_ROOT_CHECK ?= 0 | ||||||
| DESTDIR ?= | DESTDIR ?= | ||||||
| PREFIX ?= /usr/local | PREFIX ?= /usr/local | ||||||
| BIN := ./$(NAME) | BIN := ./$(NAME) | ||||||
| INSTALED_BIN := $(DESTDIR)/$(PREFIX)/bin/$(NAME) | INSTALLED_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 | ||||||
|  | GOLANGCI_LINT_BIN := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 | ||||||
|  | XGOTEXT_BIN := go run github.com/Tom5521/xgotext@v1.2.0 | ||||||
|  |  | ||||||
| .PHONY: build install clean clear uninstall check-no-root | .PHONY: build install clean clear uninstall check-no-root | ||||||
|  |  | ||||||
| build: check-no-root $(BIN) | build: check-no-root $(BIN) | ||||||
|  |  | ||||||
| export CGO_ENABLED := 0 | export CGO_ENABLED := 0 | ||||||
| $(BIN): | $(BIN): | ||||||
| 	go build -ldflags="-X 'gitea.plemya-x.ru/xpamych/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@ | 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 $@ | ||||||
|  |  | ||||||
| check-no-root: | check-no-root: | ||||||
| 	@if [[ "$$(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; \ | ||||||
| 		exit 1; \ | 		exit 1; \ | ||||||
| 	fi | 	fi | ||||||
|  |  | ||||||
| install: \ | install: \ | ||||||
| 	$(INSTALED_BIN) \ | 	$(INSTALLED_BIN) \ | ||||||
| 	$(INSTALLED_BASH_COMPLETION) \ | 	$(INSTALLED_BASH_COMPLETION) \ | ||||||
| 	$(INSTALLED_ZSH_COMPLETION) | 	$(INSTALLED_ZSH_COMPLETION) | ||||||
| 	@echo "Installation done!" | 	@echo "Installation done!" | ||||||
|  |  | ||||||
| $(INSTALED_BIN): $(BIN) | $(INSTALLED_BIN): $(BIN) | ||||||
| 	install -Dm755 $< $@ | 	install -Dm755 $< $@ | ||||||
|  | ifeq ($(CREATE_SYSTEM_RESOURCES),1) | ||||||
|  | 	@for dir in $(ROOT_DIRS); do \ | ||||||
|  | 		install -d -m 775 $$dir; \ | ||||||
|  | 		chgrp wheel $$dir; \ | ||||||
|  | 	done | ||||||
|  | else | ||||||
|  | 	@echo "Skipping root dir creation (CREATE_SYSTEM_RESOURCES=0)" | ||||||
|  | endif | ||||||
|  |  | ||||||
| $(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION) | $(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION) | ||||||
| 	install -Dm755 $< $@ | 	install -Dm755 $< $@ | ||||||
| @@ -42,9 +65,38 @@ $(INSTALLED_ZSH_COMPLETION): $(ZSH_COMPLETION) | |||||||
|  |  | ||||||
| uninstall: | uninstall: | ||||||
| 	rm -f \ | 	rm -f \ | ||||||
| 		$(INSTALED_BIN) \ | 		$(INSTALLED_BIN) \ | ||||||
| 		$(INSTALLED_BASH_COMPLETION) \ | 		$(INSTALLED_BASH_COMPLETION) \ | ||||||
| 		$(INSTALLED_ZSH_COMPLETION) | 		$(INSTALLED_ZSH_COMPLETION) | ||||||
|  |  | ||||||
| clean clear: | clean clear: | ||||||
| 	rm -f $(BIN) | 	rm -f $(BIN) | ||||||
|  |  | ||||||
|  | OLD_FILES=$(shell cat old-files) | ||||||
|  | IGNORE_OLD_FILES := $(foreach file,$(shell cat old-files),-ignore $(file)) | ||||||
|  | update-license: | ||||||
|  | 	$(ADD_LICENSE_BIN) -v -f license-header-old-files.tmpl $(OLD_FILES) | ||||||
|  | 	$(ADD_LICENSE_BIN) -v -f license-header.tmpl $(IGNORE_OLD_FILES) . | ||||||
|  |  | ||||||
|  | fmt: | ||||||
|  | 	$(GOLANGCI_LINT_BIN) run --fix | ||||||
|  |  | ||||||
|  | i18n: | ||||||
|  | 	$(XGOTEXT_BIN)  --output ./internal/translations/default.pot | ||||||
|  | 	msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot | ||||||
|  | 	msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot | ||||||
|  | 	bash scripts/i18n-badge.sh | ||||||
|  |  | ||||||
|  | test-coverage: | ||||||
|  | 	go test -tags=test ./... -v -coverpkg=./... -coverprofile=coverage.out | ||||||
|  | 	bash scripts/coverage-badge.sh | ||||||
|  |  | ||||||
|  | update-deps-cve: | ||||||
|  | 	bash scripts/update-deps-cve.sh | ||||||
|  |  | ||||||
|  | prepare-for-e2e-test: clean build | ||||||
|  | 	rm -f ./e2e-tests/alr | ||||||
|  | 	cp alr e2e-tests | ||||||
|  |  | ||||||
|  | e2e-test: prepare-for-e2e-test | ||||||
|  | 	go test -tags=e2e ./... | ||||||
							
								
								
									
										36
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								README.md
									
									
									
									
									
								
							| @@ -3,11 +3,13 @@ | |||||||
| </p> | </p> | ||||||
| <b></b> | <b></b> | ||||||
|  |  | ||||||
|  | [](https://goreportcard.com/report/gitea.plemya-x.ru/Plemya-x/ALR)   | ||||||
|  |  | ||||||
| # ALR (Any Linux Repository) | # ALR (Any Linux Repository) | ||||||
|  |  | ||||||
| ALR - это независимая от дистрибутива система сборки для Linux, аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. alr готов к общему использованию, но все еще может время от времени ломаться или заменяться. | ALR - это независимая от дистрибутива система сборки для Linux (форк [LURE](https://github.com/lure-sh/lure), аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. ALR готов к общему использованию, но все еще может время от времени ломаться или изменяться. | ||||||
|  |  | ||||||
| ALR написан на чистом Go и после сборки не имеет зависимостей. Единственное, для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе существует поддерживаемый менеджер пакетов, он будет обнаружен и использован автоматически. | ALR написан на чистом Go и после сборки не имеет зависимостей. Для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `apt-get` `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе используется поддерживаемый менеджер пакетов, то он будет обнаружен и использован автоматически. | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| @@ -18,17 +20,17 @@ ALR написан на чистом Go и после сборки не имее | |||||||
| Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду: | Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду: | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| curl -fsSL plemya-x.ru/alr/install.sh | bash | curl -fsSL https://gitea.plemya-x.ru/Plemya-x/ALR/raw/branch/master/scripts/install.sh | bash | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| **ВАЖНО**: При этом скрипт будет загружен и запущен с <https://gitea.plemya-x.ru/xpamych/ALR/install>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. | **ВАЖНО**: При этом скрипт будет загружен и запущен [скрипт](https://gitea.plemya-x.ru/Plemya-x/ALR/src/branch/master/scripts/install.sh). Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. | ||||||
|  |  | ||||||
| ### Сборка из исходного кода | ### Сборка из исходного кода | ||||||
|  |  | ||||||
| Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите: | Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите: | ||||||
|  |  | ||||||
| ```shell | ```shell | ||||||
| make build | make build -B | ||||||
| sudo make install | sudo make install | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| @@ -42,20 +44,29 @@ ALR был создан потому, что упаковка программн | |||||||
|  |  | ||||||
| ## Документация | ## Документация | ||||||
|  |  | ||||||
| Документация по всем этим вопросам находится в [Wiki](https://gitea.plemya-x.ru/xpamych/ALR/wiki/Home). | Документация находится в [Wiki](https://alr.plemya-x.ru/wiki/ALR). | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## Репозитории | ## Репозитории | ||||||
|  |  | ||||||
| Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD. Репозиторий [по-умолчанию](https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git). | Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.  | ||||||
|  |  | ||||||
|  | Например, репозиторий с ALR [alr-default](https://gitea.plemya-x.ru/Plemya-x/alr-default.git) | ||||||
|  | ``` | ||||||
|  | alr repo add alr-default https://gitea.plemya-x.ru/Plemya-x/alr-default.git | ||||||
|  | ``` | ||||||
|  | Репозиторий пакетов [alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так: | ||||||
|  | ``` | ||||||
|  | alr repo add  alr-repo https://gitea.plemya-x.ru/Plemya-x/alr-repo.git | ||||||
|  | ``` | ||||||
|  | Репозиторий Linux-Gaming [alr-LG](https://gitea.plemya-x.ru/Plemya-x/alr-LG.git) можно подключить так: | ||||||
|  | ``` | ||||||
|  | alr repo add alr-LG https://git.linux-gaming.ru/Linux-Gaming/alr-LG.git | ||||||
|  | ``` | ||||||
|  |  | ||||||
| --- | --- | ||||||
| ## Соцсети | ## Соцсети | ||||||
| VK - https://vk.com/plemya_kh |  | ||||||
|  |  | ||||||
| Discord - https://discord.com/channels/817759634105827358/1261631565084233749 |  | ||||||
|  |  | ||||||
| Telegram - https://t.me/plemyakh | Telegram - https://t.me/plemyakh | ||||||
|  |  | ||||||
| ## Спасибы | ## Спасибы | ||||||
| @@ -68,3 +79,6 @@ Telegram - https://t.me/plemyakh | |||||||
| - <https://github.com/goreleaser/nfpm> | - <https://github.com/goreleaser/nfpm> | ||||||
| - <https://github.com/charmbracelet/bubbletea> | - <https://github.com/charmbracelet/bubbletea> | ||||||
| - <https://gitlab.com/cznic/sqlite> | - <https://gitlab.com/cznic/sqlite> | ||||||
|  |  | ||||||
|  | Благодарим за активное участие в развитии проекта: | ||||||
|  | - Maks1mS <maxim@slipenko.com> | ||||||
							
								
								
									
										17
									
								
								assets/coverage-badge.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								assets/coverage-badge.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="109" height="20"> | ||||||
|  |     <linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/> | ||||||
|  |     <stop offset="1" stop-opacity=".1"/></linearGradient> | ||||||
|  |     <mask id="round"> | ||||||
|  |         <rect width="109" height="20" rx="3" fill="#fff"/> | ||||||
|  |     </mask> | ||||||
|  |     <g mask="url(#round)"><rect width="65" height="20" fill="#555"/> | ||||||
|  |         <rect x="65" width="44" height="20" fill="#e05d44"/> | ||||||
|  |         <rect width="109" height="20" fill="url(#smooth)"/> | ||||||
|  |     </g> | ||||||
|  |     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||||
|  |         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> | ||||||
|  |         <text x="33.5" y="14">coverage</text> | ||||||
|  |         <text x="86" y="15" fill="#010101" fill-opacity=".3">18.9%</text> | ||||||
|  |         <text x="86" y="14">18.9%</text> | ||||||
|  |     </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 926 B | 
							
								
								
									
										18
									
								
								assets/i18n-ru-badge.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								assets/i18n-ru-badge.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="129" height="20"> | ||||||
|  |     <linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/> | ||||||
|  |     <stop offset="1" stop-opacity=".1"/></linearGradient> | ||||||
|  |     <mask id="round"> | ||||||
|  |         <rect width="129" height="20" rx="3" fill="#fff"/> | ||||||
|  |     </mask> | ||||||
|  |     <g mask="url(#round)"> | ||||||
|  |         <rect width="75" height="20" fill="#555"/> | ||||||
|  |         <rect x="75" width="64" height="20" fill="#4c1"/> | ||||||
|  |         <rect width="129" height="20" fill="url(#smooth)"/> | ||||||
|  |     </g> | ||||||
|  |     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||||
|  |         <text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text> | ||||||
|  |         <text x="37" y="14">ru translate</text> | ||||||
|  |         <text x="100" y="15" fill="#010101" fill-opacity=".3">100.00%</text> | ||||||
|  |         <text x="100" y="14">100.00%</text> | ||||||
|  |     </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 942 B | 
							
								
								
									
										238
									
								
								build.go
									
									
									
									
									
								
							
							
						
						
									
										238
									
								
								build.go
									
									
									
									
									
								
							| @@ -1,100 +1,218 @@ | |||||||
| /* | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  * ALR - Any Linux Repository | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  * Copyright (C) 2024 Евгений Храмов | // | ||||||
|  * | // ALR - Any Linux Repository | ||||||
|  * This program is free software: you can redistribute it and/or modify | // Copyright (C) 2025 The ALR Authors | ||||||
|  * it under the terms of the GNU General Public License as published by | // | ||||||
|  * the Free Software Foundation, either version 3 of the License, or | // This program is free software: you can redistribute it and/or modify | ||||||
|  * (at your option) any later version. | // it under the terms of the GNU General Public License as published by | ||||||
|  * | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  * This program is distributed in the hope that it will be useful, | // (at your option) any later version. | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | // | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | // This program is distributed in the hope that it will be useful, | ||||||
|  * GNU General Public License for more details. | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  * | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  * You should have received a copy of the GNU General Public License | // GNU General Public License for more details. | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | // | ||||||
|  */ | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| 	"plemya-x.ru/alr/internal/config" |  | ||||||
| 	"plemya-x.ru/alr/internal/osutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" | ||||||
| 	"plemya-x.ru/alr/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	"plemya-x.ru/alr/pkg/build" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" | ||||||
| 	"plemya-x.ru/alr/pkg/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| 	"plemya-x.ru/alr/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var buildCmd = &cli.Command{ | func BuildCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
| 		Name:  "build", | 		Name:  "build", | ||||||
| 	Usage: "Build a local package", | 		Usage: gotext.Get("Build a local package"), | ||||||
| 		Flags: []cli.Flag{ | 		Flags: []cli.Flag{ | ||||||
| 			&cli.StringFlag{ | 			&cli.StringFlag{ | ||||||
| 				Name:    "script", | 				Name:    "script", | ||||||
| 				Aliases: []string{"s"}, | 				Aliases: []string{"s"}, | ||||||
| 				Value:   "alr.sh", | 				Value:   "alr.sh", | ||||||
| 			Usage:   "Path to the build script", | 				Usage:   gotext.Get("Path to the build script"), | ||||||
|  | 			}, | ||||||
|  | 			&cli.StringFlag{ | ||||||
|  | 				Name:    "subpackage", | ||||||
|  | 				Aliases: []string{"sb"}, | ||||||
|  | 				Usage:   gotext.Get("Specify subpackage in script (for multi package script only)"), | ||||||
| 			}, | 			}, | ||||||
| 			&cli.StringFlag{ | 			&cli.StringFlag{ | ||||||
| 				Name:    "package", | 				Name:    "package", | ||||||
| 				Aliases: []string{"p"}, | 				Aliases: []string{"p"}, | ||||||
| 			Usage:   "Name of the package to build and its repo (example: default/go-bin)", | 				Usage:   gotext.Get("Name of the package to build and its repo (example: default/go-bin)"), | ||||||
| 			}, | 			}, | ||||||
| 			&cli.BoolFlag{ | 			&cli.BoolFlag{ | ||||||
| 				Name:    "clean", | 				Name:    "clean", | ||||||
| 				Aliases: []string{"c"}, | 				Aliases: []string{"c"}, | ||||||
| 			Usage:   "Build package from scratch even if there's an already built package available", | 				Usage:   gotext.Get("Build package from scratch even if there's an already built package available"), | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 		ctx := c.Context | 			if err := utils.CheckUserPrivileges(); err != nil { | ||||||
| 		log := loggerctx.From(ctx) | 				return err | ||||||
|  |  | ||||||
| 		script := c.String("script") |  | ||||||
| 		if c.String("package") != "" { |  | ||||||
| 			script = filepath.Join(config.GetPaths(ctx).RepoDir, c.String("package"), "alr.sh") |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		err := repos.Pull(ctx, config.Config(ctx).Repos) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Fatal("Error pulling repositories").Err(err).Send() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		mgr := manager.Detect() |  | ||||||
| 		if mgr == nil { |  | ||||||
| 			log.Fatal("Unable to detect a supported package manager on the system").Send() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{ |  | ||||||
| 			Script:      script, |  | ||||||
| 			Manager:     mgr, |  | ||||||
| 			Clean:       c.Bool("clean"), |  | ||||||
| 			Interactive: c.Bool("interactive"), |  | ||||||
| 		}) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Fatal("Error building package").Err(err).Send() |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			wd, err := os.Getwd() | 			wd, err := os.Getwd() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 			log.Fatal("Error getting working directory").Err(err).Send() | 				return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 		for _, pkgPath := range pkgPaths { | 			ctx := c.Context | ||||||
| 			name := filepath.Base(pkgPath) |  | ||||||
| 			err = osutils.Move(pkgPath, filepath.Join(wd, name)) | 			deps, err := appbuilder. | ||||||
|  | 				New(ctx). | ||||||
|  | 				WithConfig(). | ||||||
|  | 				WithDB(). | ||||||
|  | 				WithReposNoPull(). | ||||||
|  | 				WithDistroInfo(). | ||||||
|  | 				WithManager(). | ||||||
|  | 				Build() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				log.Fatal("Error moving the package").Err(err).Send() | 				return cli.Exit(err, 1) | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			var script string | ||||||
|  | 			var packages []string | ||||||
|  |  | ||||||
|  | 			var res []*build.BuiltDep | ||||||
|  |  | ||||||
|  | 			var scriptArgs *build.BuildPackageFromScriptArgs | ||||||
|  | 			var dbArgs *build.BuildPackageFromDbArgs | ||||||
|  |  | ||||||
|  | 			buildArgs := &build.BuildArgs{ | ||||||
|  | 				Opts: &types.BuildOpts{ | ||||||
|  | 					Clean:       c.Bool("clean"), | ||||||
|  | 					Interactive: c.Bool("interactive"), | ||||||
|  | 				}, | ||||||
|  | 				PkgFormat_: build.GetPkgFormat(deps.Manager), | ||||||
|  | 				Info:       deps.Info, | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			switch { | ||||||
|  | 			case c.IsSet("script"): | ||||||
|  | 				script, err = filepath.Abs(c.String("script")) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Cannot get absolute script path"), err) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				subpackage := c.String("subpackage") | ||||||
|  |  | ||||||
|  | 				if subpackage != "" { | ||||||
|  | 					packages = append(packages, subpackage) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				scriptArgs = &build.BuildPackageFromScriptArgs{ | ||||||
|  | 					Script:    script, | ||||||
|  | 					Packages:  packages, | ||||||
|  | 					BuildArgs: *buildArgs, | ||||||
|  | 				} | ||||||
|  | 			case c.IsSet("package"): | ||||||
|  | 				// TODO: handle multiple packages | ||||||
|  | 				packageInput := c.String("package") | ||||||
|  |  | ||||||
|  | 				pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageInput}) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit("failed to find pkgs", err) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				pkg := cliutils.FlattenPkgs(ctx, pkgs, "build", c.Bool("interactive")) | ||||||
|  |  | ||||||
|  | 				if len(pkg) < 1 { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Package not found"), nil) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if pkg[0].BasePkgName != "" { | ||||||
|  | 					packages = append(packages, pkg[0].Name) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				dbArgs = &build.BuildPackageFromDbArgs{ | ||||||
|  | 					Package:   &pkg[0], | ||||||
|  | 					Packages:  packages, | ||||||
|  | 					BuildArgs: *buildArgs, | ||||||
|  | 				} | ||||||
|  | 			default: | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 			installer, installerClose, err := build.GetSafeInstaller() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer installerClose() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer scripterClose() | ||||||
|  |  | ||||||
|  | 			builder, err := build.NewMainBuilder( | ||||||
|  | 				deps.Cfg, | ||||||
|  | 				deps.Manager, | ||||||
|  | 				deps.Repos, | ||||||
|  | 				scripter, | ||||||
|  | 				installer, | ||||||
|  | 			) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if scriptArgs != nil { | ||||||
|  | 				res, err = builder.BuildPackageFromScript( | ||||||
|  | 					ctx, | ||||||
|  | 					scriptArgs, | ||||||
|  | 				) | ||||||
|  | 			} else if dbArgs != nil { | ||||||
|  | 				res, err = builder.BuildPackageFromDb( | ||||||
|  | 					ctx, | ||||||
|  | 					dbArgs, | ||||||
|  | 				) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Error building package"), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for _, pkg := range res { | ||||||
|  | 				name := filepath.Base(pkg.Path) | ||||||
|  |  | ||||||
|  | 				// Проверяем, существует ли файл перед перемещением | ||||||
|  | 				if _, err := os.Stat(pkg.Path); os.IsNotExist(err) { | ||||||
|  | 					slog.Info("Package file already moved or removed, skipping", "path", pkg.Path) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				err = osutils.Move(pkg.Path, filepath.Join(wd, name)) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			slog.Info(gotext.Get("Done")) | ||||||
|  |  | ||||||
| 			return nil | 			return nil | ||||||
| 		}, | 		}, | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										236
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | |||||||
|  | // 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 | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | - name: alr-repo | ||||||
|  |   url: https://gitea.plemya-x.ru/Plemya-x/repo-for-tests | ||||||
|  |   ref: main | ||||||
|  |   mirrors: | ||||||
|  |   - https://github.com/example/example.git | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | alr-repo/foo-pkg 1.0.0-1 | ||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | alr-repo/bar-pkg 1.0.0-1 | ||||||
|  | alr-repo/foo-pkg 1.0.0-1 | ||||||
							
								
								
									
										42
									
								
								e2e-tests/addrepo_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								e2e-tests/addrepo_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EAlrAddRepo(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"add-repo-remove-repo", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "addrepo", "--name", "alr-repo", "--url", "https://gitea.plemya-x.ru/Plemya-x/alr-repo.git") | ||||||
|  | 			execShouldNoError(t, r, "bash", "-c", "cat /etc/alr/alr.toml") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "removerepo", "--name", "alr-repo") | ||||||
|  |  | ||||||
|  | 			r.Command("bash", "-c", "cat /etc/alr/alr.toml"). | ||||||
|  | 				ExpectStdoutContains("repo = []"). | ||||||
|  | 				Run(t) | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								e2e-tests/bash_completion_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								e2e-tests/bash_completion_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EBashCompletion(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"bash-completion", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			execShouldNoError(t, r, "alr", "install", "--generate-bash-completion") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										96
									
								
								e2e-tests/common_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								e2e-tests/common_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | 	"go.alt-gnome.ru/capytest/providers/podman" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ALL_SYSTEMS []string = []string{ | ||||||
|  | 	"ubuntu-24.04", | ||||||
|  | 	"alt-sisyphus", | ||||||
|  | 	"fedora-41", | ||||||
|  | 	// "archlinux", | ||||||
|  | 	// "alpine", | ||||||
|  | 	// "opensuse-leap", | ||||||
|  | 	// "redos-8", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var AUTOREQ_AUTOPROV_SYSTEMS []string = []string{ | ||||||
|  | 	// "alt-sisyphus", | ||||||
|  | 	"fedora-41", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var RPM_SYSTEMS []string = []string{ | ||||||
|  | 	"fedora-41", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var COMMON_SYSTEMS []string = []string{ | ||||||
|  | 	"ubuntu-24.04", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func execShouldNoError(t *testing.T, r capytest.Runner, cmd string, args ...string) { | ||||||
|  | 	t.Helper() | ||||||
|  | 	r.Command(cmd, args...).ExpectSuccess().Run(t) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func execShouldError(t *testing.T, r capytest.Runner, cmd string, args ...string) { | ||||||
|  | 	t.Helper() | ||||||
|  | 	r.Command(cmd, args...).ExpectFailure().Run(t) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const REPO_NAME_FOR_E2E_TESTS = "alr-repo" | ||||||
|  | const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git" | ||||||
|  |  | ||||||
|  | func defaultPrepare(t *testing.T, r capytest.Runner) { | ||||||
|  | 	execShouldNoError(t, r, | ||||||
|  | 		"sudo", | ||||||
|  | 		"alr", | ||||||
|  | 		"repo", | ||||||
|  | 		"add", | ||||||
|  | 		REPO_NAME_FOR_E2E_TESTS, | ||||||
|  | 		REPO_URL_FOR_E2E_TESTS, | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	execShouldNoError(t, r, | ||||||
|  | 		"sudo", | ||||||
|  | 		"alr", | ||||||
|  | 		"ref", | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func runMatrixSuite(t *testing.T, name string, images []string, test func(t *testing.T, r capytest.Runner)) { | ||||||
|  | 	t.Helper() | ||||||
|  | 	for _, image := range images { | ||||||
|  | 		ts := capytest.NewTestSuite(t, podman.Provider( | ||||||
|  | 			podman.WithImage(fmt.Sprintf("ghcr.io/maks1ms/alr-e2e-test-image-%s", image)), | ||||||
|  | 			podman.WithVolumes("./alr:/tmp/alr"), | ||||||
|  | 			podman.WithPrivileged(true), | ||||||
|  | 		)) | ||||||
|  | 		ts.BeforeEach(func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			execShouldNoError(t, r, "/bin/alr-test-setup", "alr-install") | ||||||
|  | 			execShouldNoError(t, r, "/bin/alr-test-setup", "passwordless-sudo-setup") | ||||||
|  | 		}) | ||||||
|  | 		ts.Run(fmt.Sprintf("%s/%s", name, image), test) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								e2e-tests/firejailed_package_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								e2e-tests/firejailed_package_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"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'") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								e2e-tests/fix_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								e2e-tests/fix_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EAlrFix(t *testing.T) { | ||||||
|  | 	runMatrixSuite(t, "run-fix", COMMON_SYSTEMS, func(t *testing.T, r capytest.Runner) { | ||||||
|  | 		r.Command("alr", "fix"). | ||||||
|  | 			ExpectStderrContains("--> Done"). | ||||||
|  | 			ExpectSuccess(). | ||||||
|  | 			Run(t) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								e2e-tests/group_and_summary_field_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								e2e-tests/group_and_summary_field_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EGroupAndSummaryField(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"group-and-summary-field", | ||||||
|  | 		RPM_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group.Resolved}}\" | grep ^System/Base$") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary.Resolved}}\" | grep \"^Custom summary$\"") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								e2e-tests/issue_129_repo_toml_import_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								e2e-tests/issue_129_repo_toml_import_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // 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) | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										63
									
								
								e2e-tests/issue_130_install_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								e2e-tests/issue_130_install_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue130Install(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"alr install {repo}/{package}", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			t.Parallel() | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  |  | ||||||
|  | 			r.Command("sudo", "alr", "in", fmt.Sprintf("%s/foo-pkg", REPO_NAME_FOR_E2E_TESTS)). | ||||||
|  | 				ExpectSuccess(). | ||||||
|  | 				Run(t) | ||||||
|  |  | ||||||
|  | 			r.Command("sudo", "alr", "in", fmt.Sprintf("%s/bar-pkg", "NOT_REPO_NAME_FOR_E2E_TESTS")). | ||||||
|  | 				ExpectFailure(). | ||||||
|  | 				Run(t) | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"alr install {package}+{repo}", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			t.Parallel() | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  |  | ||||||
|  | 			r.Command("sudo", "alr", "in", fmt.Sprintf("foo-pkg+%s", REPO_NAME_FOR_E2E_TESTS)). | ||||||
|  | 				ExpectSuccess(). | ||||||
|  | 				Run(t) | ||||||
|  |  | ||||||
|  | 			r.Command("sudo", "alr", "in", fmt.Sprintf("bar-pkg+%s", "NOT_REPO_NAME_FOR_E2E_TESTS")). | ||||||
|  | 				ExpectFailure(). | ||||||
|  | 				Run(t) | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								e2e-tests/issue_32_interactive_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								e2e-tests/issue_32_interactive_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue32Interactive(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"issue-32-interactive", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			execShouldNoError(t, r, "alr", "--interactive=false", "remove", "ca-certificates") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "remove", "openssl") | ||||||
|  | 			execShouldNoError(t, r, "alr", "fix") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "apt-get", "update") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "install", "ca-certificates") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								e2e-tests/issue_41_autoreq_skiplist_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								e2e-tests/issue_41_autoreq_skiplist_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue41AutoreqSkiplist(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"issue-41-autoreq-skiplist", | ||||||
|  | 		AUTOREQ_AUTOPROV_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "alr", "build", "-p", "alr-repo/test-autoreq-autoprov") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/sh$\"") | ||||||
|  | 			execShouldError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/bash$\"") | ||||||
|  | 			execShouldError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/zsh$\"") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								e2e-tests/issue_50_install_multiple_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								e2e-tests/issue_50_install_multiple_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue50InstallMultiple(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"issue-50-install-multiple", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg") | ||||||
|  | 			execShouldNoError(t, r, "cat", "/opt/foo") | ||||||
|  | 			execShouldNoError(t, r, "cat", "/opt/bar") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								e2e-tests/issue_53_lc_all_c_info_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								e2e-tests/issue_53_lc_all_c_info_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue53LcAllCInfo(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"issue-53-lc-all-c-info", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "bash", "-c", "LANG=C alr info foo-pkg") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								e2e-tests/issue_59_rm_completion_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								e2e-tests/issue_59_rm_completion_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue59RmCompletion(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"issue-59-rm-completion", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^foo-pkg$") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^bar-pkg$") | ||||||
|  | 			execShouldError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^test-autoreq-autoprov$") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										50
									
								
								e2e-tests/issue_62_list_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								e2e-tests/issue_62_list_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | // 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) | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								e2e-tests/issue_72_install_with_deps_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								e2e-tests/issue_72_install_with_deps_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue72InstallWithDeps(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"issue-72-install-with-deps", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "in", "test-app-with-lib") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								e2e-tests/issue_74_upgradable_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								e2e-tests/issue_74_upgradable_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue74Upgradable(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"issue-74-upgradable", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7") | ||||||
|  | 			execShouldNoError(t, r, "alr", "ref") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "in", "bar-pkg") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "d9a3541561") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "ref") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								e2e-tests/issue_75_ref_specify_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								e2e-tests/issue_75_ref_specify_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue75InstallWithDeps(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"issue-75-ref-specify", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								e2e-tests/issue_76_single_package_repo_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								e2e-tests/issue_76_single_package_repo_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | // 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") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										49
									
								
								e2e-tests/issue_78_mirrors_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								e2e-tests/issue_78_mirrors_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"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") | ||||||
|  | 	}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								e2e-tests/issue_81_multiple_packages_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								e2e-tests/issue_81_multiple_packages_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue81MultiplePackages(t *testing.T) { | ||||||
|  | 	runMatrixSuite( | ||||||
|  | 		t, | ||||||
|  | 		"issue-81-multiple-packages", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r capytest.Runner) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "in", "first-package-with-dashes") | ||||||
|  | 			execShouldNoError(t, r, "cat", "/opt/first-package") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								e2e-tests/issue_91_set_repo_ref_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								e2e-tests/issue_91_set_repo_ref_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // 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") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								e2e-tests/issue_94_twice_build_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								e2e-tests/issue_94_twice_build_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | // 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")) | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								e2e-tests/issue_95_config_command_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								e2e-tests/issue_95_config_command_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | // 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\"") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								e2e-tests/version_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								e2e-tests/version_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"go.alt-gnome.ru/capytest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EAlrVersion(t *testing.T) { | ||||||
|  | 	runMatrixSuite(t, "version", COMMON_SYSTEMS, func(t *testing.T, r capytest.Runner) { | ||||||
|  | 		r.Command("alr", "version"). | ||||||
|  | 			ExpectStderrRegex(`^v\d+\.\d+\.\d+(?:-\d+-g[a-f0-9]+)?\n$`). | ||||||
|  | 			ExpectStdoutEmpty(). | ||||||
|  | 			ExpectSuccess(). | ||||||
|  | 			Run(t) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										231
									
								
								fix.go
									
									
									
									
									
								
							
							
						
						
									
										231
									
								
								fix.go
									
									
									
									
									
								
							| @@ -1,64 +1,219 @@ | |||||||
| /* | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  * ALR - Any Linux Repository | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  * Copyright (C) 2024 Евгений Храмов | // | ||||||
|  * | // ALR - Any Linux Repository | ||||||
|  * This program is free software: you can redistribute it and/or modify | // Copyright (C) 2025 The ALR Authors | ||||||
|  * it under the terms of the GNU General Public License as published by | // | ||||||
|  * the Free Software Foundation, either version 3 of the License, or | // This program is free software: you can redistribute it and/or modify | ||||||
|  * (at your option) any later version. | // it under the terms of the GNU General Public License as published by | ||||||
|  * | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  * This program is distributed in the hope that it will be useful, | // (at your option) any later version. | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | // | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | // This program is distributed in the hope that it will be useful, | ||||||
|  * GNU General Public License for more details. | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  * | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  * You should have received a copy of the GNU General Public License | // GNU General Public License for more details. | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | // | ||||||
|  */ | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"io/fs" | ||||||
|  | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"path/filepath" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| 	"plemya-x.ru/alr/internal/config" |  | ||||||
| 	"plemya-x.ru/alr/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"plemya-x.ru/alr/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var fixCmd = &cli.Command{ | // 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 { | ||||||
|  | 	return &cli.Command{ | ||||||
| 		Name:  "fix", | 		Name:  "fix", | ||||||
| 	Usage: "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 { | ||||||
|  | 			// Команда выполняется от текущего пользователя | ||||||
|  | 			// При необходимости будет запрошен sudo для удаления файлов root | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
| 		log := loggerctx.From(ctx) |  | ||||||
|  |  | ||||||
| 		db.Close() | 			deps, err := appbuilder. | ||||||
| 		paths := config.GetPaths(ctx) | 				New(ctx). | ||||||
|  | 				WithConfig(). | ||||||
| 		log.Info("Removing cache directory").Send() | 				Build() | ||||||
|  |  | ||||||
| 		err := os.RemoveAll(paths.CacheDir) |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 			log.Fatal("Unable to remove cache directory").Err(err).Send() | 				return cli.Exit(err, 1) | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			cfg := deps.Cfg | ||||||
|  |  | ||||||
|  | 			paths := cfg.GetPaths() | ||||||
|  |  | ||||||
|  | 			slog.Info(gotext.Get("Clearing cache and temporary directories")) | ||||||
|  |  | ||||||
|  | 			// Проверяем, существует ли директория кэша | ||||||
|  | 			dir, err := os.Open(paths.CacheDir) | ||||||
|  | 			if err != nil { | ||||||
|  | 				if os.IsNotExist(err) { | ||||||
|  | 					// Директория не существует, просто создадим её позже | ||||||
|  | 					slog.Info(gotext.Get("Cache directory does not exist, will create it")) | ||||||
|  | 				} else { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err) | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				defer dir.Close() | ||||||
|  |  | ||||||
|  | 				entries, err := dir.Readdirnames(-1) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err) | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 		log.Info("Rebuilding cache").Send() | 				for _, entry := range entries { | ||||||
|  | 					fullPath := filepath.Join(paths.CacheDir, entry) | ||||||
|  |  | ||||||
| 		err = os.MkdirAll(paths.CacheDir, 0o755) | 					// Пробуем сделать файлы доступными для записи | ||||||
| 		if err != nil { | 					if err := makeWritableRecursive(fullPath); err != nil { | ||||||
| 			log.Fatal("Unable to create new cache directory").Err(err).Send() | 						slog.Debug("Failed to make path writable", "path", fullPath, "error", err) | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 		err = repos.Pull(ctx, config.Config(ctx).Repos) | 					// Пробуем удалить | ||||||
|  | 					err = os.RemoveAll(fullPath) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 			log.Fatal("Error pulling repos").Err(err).Send() | 						// Если не получилось удалить, пробуем через 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 | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 		log.Info("Done").Send() | 			// Очищаем временные директории | ||||||
|  | 			slog.Info(gotext.Get("Clearing temporary directory")) | ||||||
|  | 			tmpDir := "/tmp/alr" | ||||||
|  | 			if _, err := os.Stat(tmpDir); err == nil { | ||||||
|  | 				// Директория существует, пробуем очистить | ||||||
|  | 				err = os.RemoveAll(tmpDir) | ||||||
|  | 				if err != nil { | ||||||
|  | 					// Если не получилось удалить, пробуем через sudo | ||||||
|  | 					slog.Warn(gotext.Get("Unable to remove temporary directory as current user, trying with sudo")) | ||||||
|  | 					sudoCmd := execWithPrivileges("rm", "-rf", tmpDir) | ||||||
|  | 					if sudoErr := sudoCmd.Run(); sudoErr != nil { | ||||||
|  | 						slog.Error(gotext.Get("Unable to remove temporary directory"), "error", err) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Создаем базовый каталог /tmp/alr с владельцем root:wheel и правами 2775 | ||||||
|  | 			err = utils.EnsureTempDirWithRootOwner(tmpDir, 0o2775) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Warn(gotext.Get("Unable to create temporary directory"), "error", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Создаем каталог dl с правами для группы wheel | ||||||
|  | 			dlDir := filepath.Join(tmpDir, "dl") | ||||||
|  | 			err = utils.EnsureTempDirWithRootOwner(dlDir, 0o2775) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Warn(gotext.Get("Unable to create download directory"), "error", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Создаем каталог pkgs с правами для группы wheel | ||||||
|  | 			pkgsDir := filepath.Join(tmpDir, "pkgs") | ||||||
|  | 			err = utils.EnsureTempDirWithRootOwner(pkgsDir, 0o2775) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Warn(gotext.Get("Unable to create packages directory"), "error", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Исправляем права на все существующие файлы в /tmp/alr, если там что-то есть | ||||||
|  | 			if _, err := os.Stat(tmpDir); err == nil { | ||||||
|  | 				slog.Info(gotext.Get("Fixing permissions on temporary files")) | ||||||
|  | 				 | ||||||
|  | 				// Проверяем, есть ли файлы в директории | ||||||
|  | 				entries, err := os.ReadDir(tmpDir) | ||||||
|  | 				if err == nil && len(entries) > 0 { | ||||||
|  | 					group := utils.GetPrivilegedGroup() | ||||||
|  | 					fixCmd := execWithPrivileges("chown", "-R", "root:"+group, tmpDir) | ||||||
|  | 					if fixErr := fixCmd.Run(); fixErr != nil { | ||||||
|  | 						slog.Warn(gotext.Get("Unable to fix file ownership"), "error", fixErr) | ||||||
|  | 					} | ||||||
|  | 					 | ||||||
|  | 					fixCmd = execWithPrivileges("chmod", "-R", "2775", tmpDir) | ||||||
|  | 					if fixErr := fixCmd.Run(); fixErr != nil { | ||||||
|  | 						slog.Warn(gotext.Get("Unable to fix file permissions"), "error", fixErr) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			slog.Info(gotext.Get("Rebuilding cache")) | ||||||
|  |  | ||||||
|  | 			// Создаем директорию кэша с правильными правами | ||||||
|  | 			slog.Info(gotext.Get("Creating cache directory")) | ||||||
|  | 			err = utils.EnsureTempDirWithRootOwner(paths.CacheDir, 0o2775) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			deps, err = appbuilder. | ||||||
|  | 				New(ctx). | ||||||
|  | 				WithConfig(). | ||||||
|  | 				WithDB(). | ||||||
|  | 				WithReposForcePull(). | ||||||
|  | 				Build() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cli.Exit(err, 1) | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			slog.Info(gotext.Get("Done")) | ||||||
|  |  | ||||||
| 			return nil | 			return nil | ||||||
| 		}, | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										62
									
								
								gen.go
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								gen.go
									
									
									
									
									
								
							| @@ -1,24 +1,42 @@ | |||||||
|  | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  | // | ||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| 	"plemya-x.ru/alr/pkg/gen" |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/gen" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var genCmd = &cli.Command{ | func GenCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
| 		Name:    "generate", | 		Name:    "generate", | ||||||
| 	Usage:   "Generate a ALR script from a template", | 		Usage:   gotext.Get("Generate a ALR script from a template"), | ||||||
| 		Aliases: []string{"gen"}, | 		Aliases: []string{"gen"}, | ||||||
| 		Subcommands: []*cli.Command{ | 		Subcommands: []*cli.Command{ | ||||||
| 		genPipCmd, | 			{ | ||||||
| 	}, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var genPipCmd = &cli.Command{ |  | ||||||
| 				Name:  "pip", | 				Name:  "pip", | ||||||
| 	Usage: "Generate a ALR script for a pip module", | 				Usage: gotext.Get("Generate a ALR script for a pip module"), | ||||||
| 				Flags: []cli.Flag{ | 				Flags: []cli.Flag{ | ||||||
| 					&cli.StringFlag{ | 					&cli.StringFlag{ | ||||||
| 						Name:     "name", | 						Name:     "name", | ||||||
| @@ -42,4 +60,30 @@ var genPipCmd = &cli.Command{ | |||||||
| 						Description: c.String("description"), | 						Description: c.String("description"), | ||||||
| 					}) | 					}) | ||||||
| 				}, | 				}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				Name:  "aur", | ||||||
|  | 				Usage: gotext.Get("Generate a ALR script for an AUR package"), | ||||||
|  | 				Flags: []cli.Flag{ | ||||||
|  | 					&cli.StringFlag{ | ||||||
|  | 						Name:     "name", | ||||||
|  | 						Aliases:  []string{"n"}, | ||||||
|  | 						Required: true, | ||||||
|  | 						Usage:    gotext.Get("Name of the AUR package"), | ||||||
|  | 					}, | ||||||
|  | 					&cli.StringFlag{ | ||||||
|  | 						Name:    "version", | ||||||
|  | 						Aliases: []string{"v"}, | ||||||
|  | 						Usage:   gotext.Get("Version of the package (optional, uses latest if not specified)"), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				Action: func(c *cli.Context) error { | ||||||
|  | 					return gen.AUR(os.Stdout, gen.AUROptions{ | ||||||
|  | 						Name:    c.String("name"), | ||||||
|  | 						Version: c.String("version"), | ||||||
|  | 					}) | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										251
									
								
								generators/alrsh-package/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								generators/alrsh-package/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,251 @@ | |||||||
|  | // 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" | ||||||
|  | } | ||||||
							
								
								
									
										416
									
								
								generators/plugin-generator/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										416
									
								
								generators/plugin-generator/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,416 @@ | |||||||
|  | // 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) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										145
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										145
									
								
								go.mod
									
									
									
									
									
								
							| @@ -1,50 +1,59 @@ | |||||||
| module plemya-x.ru/alr | module gitea.plemya-x.ru/Plemya-x/ALR | ||||||
|  |  | ||||||
| go 1.21 | go 1.24.4 | ||||||
|  |  | ||||||
| toolchain go1.21.3 |  | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
|  | 	gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 | ||||||
| 	github.com/AlecAivazis/survey/v2 v2.3.7 | 	github.com/AlecAivazis/survey/v2 v2.3.7 | ||||||
| 	github.com/PuerkitoBio/purell v1.2.0 | 	github.com/PuerkitoBio/purell v1.2.0 | ||||||
| 	github.com/alecthomas/chroma/v2 v2.9.1 | 	github.com/alecthomas/chroma/v2 v2.9.1 | ||||||
| 	github.com/charmbracelet/bubbles v0.16.1 | 	github.com/bmatcuk/doublestar/v4 v4.8.1 | ||||||
| 	github.com/charmbracelet/bubbletea v0.24.2 | 	github.com/charmbracelet/bubbles v0.20.0 | ||||||
| 	github.com/charmbracelet/lipgloss v0.8.0 | 	github.com/charmbracelet/bubbletea v1.2.4 | ||||||
| 	github.com/go-git/go-billy/v5 v5.5.0 | 	github.com/charmbracelet/lipgloss v1.0.0 | ||||||
| 	github.com/go-git/go-git/v5 v5.9.0 | 	github.com/charmbracelet/log v0.4.0 | ||||||
| 	github.com/goreleaser/nfpm/v2 v2.33.0 | 	github.com/go-git/go-billy/v5 v5.6.0 | ||||||
| 	github.com/jmoiron/sqlx v1.3.5 | 	github.com/go-git/go-git/v5 v5.13.0 | ||||||
| 	github.com/mattn/go-isatty v0.0.19 | 	github.com/goccy/go-yaml v1.18.0 | ||||||
|  | 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 | ||||||
|  | 	github.com/goreleaser/nfpm/v2 v2.41.0 | ||||||
|  | 	github.com/hashicorp/go-hclog v0.14.1 | ||||||
|  | 	github.com/hashicorp/go-plugin v1.6.3 | ||||||
|  | 	github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 | ||||||
|  | 	github.com/knadh/koanf/parsers/toml/v2 v2.2.0 | ||||||
|  | 	github.com/knadh/koanf/providers/confmap v1.0.0 | ||||||
|  | 	github.com/knadh/koanf/providers/env v1.1.0 | ||||||
|  | 	github.com/knadh/koanf/providers/file v1.2.0 | ||||||
|  | 	github.com/knadh/koanf/v2 v2.2.1 | ||||||
|  | 	github.com/leonelquinteros/gotext v1.7.0 | ||||||
|  | 	github.com/mattn/go-isatty v0.0.20 | ||||||
| 	github.com/mholt/archiver/v4 v4.0.0-alpha.8 | 	github.com/mholt/archiver/v4 v4.0.0-alpha.8 | ||||||
| 	github.com/mitchellh/mapstructure v1.5.0 | 	github.com/mitchellh/mapstructure v1.5.0 | ||||||
| 	github.com/muesli/reflow v0.3.0 | 	github.com/muesli/reflow v0.3.0 | ||||||
| 	github.com/pelletier/go-toml/v2 v2.1.0 | 	github.com/pelletier/go-toml/v2 v2.2.4 | ||||||
| 	github.com/schollz/progressbar/v3 v3.13.1 | 	github.com/stretchr/testify v1.10.0 | ||||||
| 	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.elara.ws/logger v0.0.0-20230421022458-e80700db2090 | 	go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 | ||||||
| 	go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6 | 	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.13.0 | 	golang.org/x/crypto v0.36.0 | ||||||
| 	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 | 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 | ||||||
| 	golang.org/x/sys v0.12.0 | 	golang.org/x/sys v0.33.0 | ||||||
| 	golang.org/x/text v0.13.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.7.0 | 	mvdan.cc/sh/v3 v3.10.0 | ||||||
| 	plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283 | 	xorm.io/xorm v1.3.9 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	dario.cat/mergo v1.0.0 // indirect | 	dario.cat/mergo v1.0.1 // indirect | ||||||
| 	github.com/AlekSi/pointer v1.2.0 // indirect | 	github.com/AlekSi/pointer v1.2.0 // indirect | ||||||
| 	github.com/Masterminds/goutils v1.1.1 // indirect | 	github.com/Masterminds/goutils v1.1.1 // indirect | ||||||
| 	github.com/Masterminds/semver/v3 v3.2.1 // indirect | 	github.com/Masterminds/semver/v3 v3.3.0 // indirect | ||||||
| 	github.com/Masterminds/sprig/v3 v3.2.3 // indirect | 	github.com/Masterminds/sprig/v3 v3.2.3 // indirect | ||||||
| 	github.com/Microsoft/go-winio v0.6.1 // indirect | 	github.com/Microsoft/go-winio v0.6.1 // indirect | ||||||
| 	github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect | 	github.com/ProtonMail/go-crypto v1.1.3 // indirect | ||||||
| 	github.com/acomagu/bufpipe v1.0.4 // 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 | ||||||
| @@ -52,68 +61,99 @@ require ( | |||||||
| 	github.com/bodgit/sevenzip v1.3.0 // indirect | 	github.com/bodgit/sevenzip v1.3.0 // indirect | ||||||
| 	github.com/bodgit/windows v1.0.0 // indirect | 	github.com/bodgit/windows v1.0.0 // indirect | ||||||
| 	github.com/cavaliergopher/cpio v1.0.1 // indirect | 	github.com/cavaliergopher/cpio v1.0.1 // indirect | ||||||
| 	github.com/cloudflare/circl v1.3.3 // indirect | 	github.com/charmbracelet/harmonica v0.2.0 // indirect | ||||||
|  | 	github.com/charmbracelet/x/ansi v0.4.5 // indirect | ||||||
|  | 	github.com/charmbracelet/x/term v0.2.1 // indirect | ||||||
|  | 	github.com/cloudflare/circl v1.6.1 // indirect | ||||||
| 	github.com/connesc/cipherio v0.2.1 // indirect | 	github.com/connesc/cipherio v0.2.1 // indirect | ||||||
| 	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect | 	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect | ||||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect | 	github.com/creack/pty v1.1.24 // indirect | ||||||
| 	github.com/cyphar/filepath-securejoin v0.2.4 // indirect | 	github.com/cyphar/filepath-securejoin v0.2.5 // indirect | ||||||
|  | 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||||
| 	github.com/dlclark/regexp2 v1.10.0 // indirect | 	github.com/dlclark/regexp2 v1.10.0 // indirect | ||||||
| 	github.com/dsnet/compress v0.0.1 // indirect | 	github.com/dsnet/compress v0.0.1 // indirect | ||||||
| 	github.com/dustin/go-humanize v1.0.1 // indirect | 	github.com/dustin/go-humanize v1.0.1 // indirect | ||||||
| 	github.com/emirpasic/gods v1.18.1 // indirect | 	github.com/emirpasic/gods v1.18.1 // indirect | ||||||
|  | 	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect | ||||||
|  | 	github.com/fatih/color v1.7.0 // indirect | ||||||
|  | 	github.com/fsnotify/fsnotify v1.9.0 // indirect | ||||||
|  | 	github.com/gkampitakis/ciinfo v0.3.2 // indirect | ||||||
|  | 	github.com/gkampitakis/go-diff v1.3.2 // indirect | ||||||
|  | 	github.com/gkampitakis/go-snaps v0.5.13 // indirect | ||||||
| 	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect | 	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect | ||||||
|  | 	github.com/go-logfmt/logfmt v0.6.0 // indirect | ||||||
|  | 	github.com/go-viper/mapstructure/v2 v2.3.0 // indirect | ||||||
| 	github.com/gobwas/glob v0.2.3 // indirect | 	github.com/gobwas/glob v0.2.3 // indirect | ||||||
|  | 	github.com/goccy/go-json v0.8.1 // indirect | ||||||
| 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||||||
|  | 	github.com/golang/protobuf v1.5.3 // indirect | ||||||
| 	github.com/golang/snappy v0.0.4 // indirect | 	github.com/golang/snappy v0.0.4 // indirect | ||||||
| 	github.com/google/rpmpack v0.5.0 // indirect | 	github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect | ||||||
| 	github.com/google/uuid v1.3.0 // indirect | 	github.com/google/uuid v1.6.0 // indirect | ||||||
| 	github.com/gookit/color v1.5.1 // indirect | 	github.com/goreleaser/chglog v0.6.1 // indirect | ||||||
| 	github.com/goreleaser/chglog v0.5.0 // indirect |  | ||||||
| 	github.com/goreleaser/fileglob v1.3.0 // indirect | 	github.com/goreleaser/fileglob v1.3.0 // indirect | ||||||
| 	github.com/hashicorp/errwrap v1.0.0 // indirect | 	github.com/hashicorp/errwrap v1.0.0 // indirect | ||||||
| 	github.com/hashicorp/go-multierror v1.1.1 // indirect | 	github.com/hashicorp/go-multierror v1.1.1 // indirect | ||||||
|  | 	github.com/hashicorp/yamux v0.1.1 // indirect | ||||||
| 	github.com/huandu/xstrings v1.3.3 // indirect | 	github.com/huandu/xstrings v1.3.3 // indirect | ||||||
| 	github.com/imdario/mergo v0.3.16 // indirect | 	github.com/imdario/mergo v0.3.16 // indirect | ||||||
| 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | ||||||
|  | 	github.com/json-iterator/go v1.1.12 // indirect | ||||||
| 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | ||||||
| 	github.com/kevinburke/ssh_config v1.2.0 // indirect | 	github.com/kevinburke/ssh_config v1.2.0 // indirect | ||||||
| 	github.com/klauspost/compress v1.17.0 // 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/mattn/go-colorable v0.1.2 // indirect | 	github.com/maruel/natural v1.1.1 // 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.15 // 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/colorstring v0.0.0-20190213212951-d06e56a500db // indirect |  | ||||||
| 	github.com/mitchellh/copystructure v1.2.0 // indirect | 	github.com/mitchellh/copystructure v1.2.0 // indirect | ||||||
| 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | ||||||
| 	github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect | 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||||
|  | 	github.com/modern-go/reflect2 v1.0.2 // indirect | ||||||
|  | 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect | ||||||
| 	github.com/muesli/cancelreader v0.2.2 // indirect | 	github.com/muesli/cancelreader v0.2.2 // indirect | ||||||
| 	github.com/muesli/termenv v0.15.2 // indirect | 	github.com/muesli/termenv v0.15.2 // indirect | ||||||
| 	github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect | 	github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect | ||||||
|  | 	github.com/oklog/run v1.0.0 // indirect | ||||||
| 	github.com/pierrec/lz4/v4 v4.1.15 // indirect | 	github.com/pierrec/lz4/v4 v4.1.15 // indirect | ||||||
| 	github.com/pjbgf/sha1cd v0.3.0 // indirect | 	github.com/pjbgf/sha1cd v0.3.0 // indirect | ||||||
|  | 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||||
| 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | ||||||
| 	github.com/rivo/uniseg v0.4.4 // 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.2.0 // indirect | 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect | ||||||
| 	github.com/shopspring/decimal v1.2.0 // indirect | 	github.com/shopspring/decimal v1.3.1 // indirect | ||||||
| 	github.com/skeema/knownhosts v1.2.0 // indirect | 	github.com/skeema/knownhosts v1.3.0 // indirect | ||||||
| 	github.com/spf13/cast v1.5.1 // indirect | 	github.com/spf13/cast v1.7.1 // indirect | ||||||
|  | 	github.com/syndtr/goleveldb v1.0.0 // indirect | ||||||
| 	github.com/therootcompany/xz v1.0.1 // indirect | 	github.com/therootcompany/xz v1.0.1 // indirect | ||||||
| 	github.com/ulikunitz/xz v0.5.11 // 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/vmihailenco/tagparser/v2 v2.0.0 // indirect | 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect | ||||||
| 	github.com/xanzy/ssh-agent v0.3.3 // indirect | 	github.com/xanzy/ssh-agent v0.3.3 // indirect | ||||||
| 	github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect |  | ||||||
| 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect | 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect | ||||||
| 	gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect | 	gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect | ||||||
| 	go4.org v0.0.0-20200411211856-f5505b9728dd // indirect | 	go4.org v0.0.0-20200411211856-f5505b9728dd // indirect | ||||||
| 	golang.org/x/mod v0.12.0 // indirect | 	golang.org/x/mod v0.19.0 // indirect | ||||||
| 	golang.org/x/net v0.15.0 // indirect | 	golang.org/x/net v0.38.0 // indirect | ||||||
| 	golang.org/x/sync v0.3.0 // indirect | 	golang.org/x/sync v0.12.0 // indirect | ||||||
| 	golang.org/x/term v0.12.0 // indirect | 	golang.org/x/term v0.30.0 // indirect | ||||||
| 	golang.org/x/tools v0.13.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/grpc v1.67.3 // indirect | ||||||
|  | 	google.golang.org/protobuf v1.36.1 // indirect | ||||||
| 	gopkg.in/warnings.v0 v0.1.2 // indirect | 	gopkg.in/warnings.v0 v0.1.2 // indirect | ||||||
|  | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
| 	lukechampine.com/uint128 v1.2.0 // indirect | 	lukechampine.com/uint128 v1.2.0 // indirect | ||||||
| 	modernc.org/cc/v3 v3.40.0 // indirect | 	modernc.org/cc/v3 v3.40.0 // indirect | ||||||
| 	modernc.org/ccgo/v3 v3.16.13 // indirect | 	modernc.org/ccgo/v3 v3.16.13 // indirect | ||||||
| @@ -123,4 +163,5 @@ require ( | |||||||
| 	modernc.org/opt v0.1.3 // indirect | 	modernc.org/opt v0.1.3 // indirect | ||||||
| 	modernc.org/strutil v1.1.3 // indirect | 	modernc.org/strutil v1.1.3 // indirect | ||||||
| 	modernc.org/token v1.0.1 // indirect | 	modernc.org/token v1.0.1 // indirect | ||||||
|  | 	xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										384
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										384
									
								
								go.sum
									
									
									
									
									
								
							| @@ -14,22 +14,26 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k | |||||||
| cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= | ||||||
| cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= | ||||||
| cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= | ||||||
| dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= | dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= | ||||||
| dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= | dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= | ||||||
| dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= | ||||||
|  | gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= | ||||||
|  | gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= | ||||||
|  | gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8= | ||||||
|  | gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw= | ||||||
| github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= | github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= | ||||||
| github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= | github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= | ||||||
| github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= | github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= | ||||||
| github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0= | github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0= | ||||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||||||
| github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= | ||||||
| github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= | github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= | ||||||
| github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= | github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= | ||||||
| github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= | ||||||
| github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= | ||||||
| github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= | github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= | ||||||
| github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= | github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= | ||||||
| github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= | github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= | ||||||
| github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= | github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= | ||||||
| github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= | github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= | ||||||
| github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= | ||||||
| @@ -37,16 +41,14 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc | |||||||
| github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= | github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= | ||||||
| github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= | ||||||
| github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= | ||||||
| github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= | github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= | ||||||
| github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= | github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= | ||||||
| github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= | github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= | ||||||
| github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= | github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= | ||||||
| github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s= | github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s= | ||||||
| github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs= | github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs= | ||||||
| github.com/PuerkitoBio/purell v1.2.0 h1:/Jdm5QfyM8zdlqT6WVZU4cfP23sot6CEHA4CS49Ezig= | github.com/PuerkitoBio/purell v1.2.0 h1:/Jdm5QfyM8zdlqT6WVZU4cfP23sot6CEHA4CS49Ezig= | ||||||
| github.com/PuerkitoBio/purell v1.2.0/go.mod h1:OhLRTaaIzhvIyofkJfB24gokC7tM42Px5UhoT32THBk= | github.com/PuerkitoBio/purell v1.2.0/go.mod h1:OhLRTaaIzhvIyofkJfB24gokC7tM42Px5UhoT32THBk= | ||||||
| github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= |  | ||||||
| github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= |  | ||||||
| github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= | github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= | ||||||
| github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= | github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= | ||||||
| github.com/alecthomas/chroma/v2 v2.9.1 h1:0O3lTQh9FxazJ4BYE/MOi/vDGuHn7B+6Bu902N2UZvU= | github.com/alecthomas/chroma/v2 v2.9.1 h1:0O3lTQh9FxazJ4BYE/MOi/vDGuHn7B+6Bu902N2UZvU= | ||||||
| @@ -63,43 +65,51 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE | |||||||
| github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= | ||||||
| github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= | github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= | ||||||
| github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= | github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= | ||||||
|  | github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= | ||||||
|  | github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= | ||||||
| github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= | github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= | ||||||
| github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8= | github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8= | ||||||
| github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY= | github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY= | ||||||
| github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM= | github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM= | ||||||
| github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA= | github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA= | ||||||
| github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= | github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= | ||||||
| github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= | github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= | ||||||
| github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11 h1:IRrDwVlWQr6kS1U8/EtyA1+EHcc4yl8pndcqXWrEamg= | github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= | ||||||
| github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11/go.mod h1:je2KZ+LxaCNvCoKg32jtOIULcFogJKcL1ZWUaIBjKj0= |  | ||||||
| github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= | github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= | ||||||
| github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= | github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= | ||||||
| github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= | github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= | ||||||
| github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= | github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= | ||||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||||||
| github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= | github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= | ||||||
| github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= | github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= | ||||||
| github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= | github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= | ||||||
| github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= | github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= | ||||||
| github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU= | github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= | ||||||
| github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU= | github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= | ||||||
|  | github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= | ||||||
|  | github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= | ||||||
|  | github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= | ||||||
|  | github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= | ||||||
|  | github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM= | ||||||
|  | github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= | ||||||
|  | github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= | ||||||
|  | github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= | ||||||
| github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= | ||||||
| github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | ||||||
| github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= | ||||||
| github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||||||
| github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= | github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= | ||||||
| github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= | github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= | ||||||
| github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= | github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= | ||||||
| github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= | github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= | ||||||
| github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= | github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= | ||||||
| github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||||||
| github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||||
| github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= |  | ||||||
| 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.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= | github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= | ||||||
| github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= | github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= | ||||||
| github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= | github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= | ||||||
| github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= | github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= | ||||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| @@ -110,30 +120,53 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh | |||||||
| github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= | ||||||
| github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= | ||||||
| github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= | ||||||
| github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= | github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= | ||||||
| github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= | 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= | ||||||
| github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= | ||||||
| github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||||
| github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | ||||||
| github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= | ||||||
| github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= | ||||||
| github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= | ||||||
| github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | ||||||
|  | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= | ||||||
|  | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= | ||||||
|  | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||||||
|  | github.com/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/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= | ||||||
| github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= | ||||||
| github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= | ||||||
| github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= | github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= | ||||||
| github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= | github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= | ||||||
| github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= | ||||||
| github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= | ||||||
| github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY= | github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= | ||||||
| github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0= | github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= | ||||||
| github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= | ||||||
| github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||||||
| github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= | github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= | ||||||
| github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= | ||||||
|  | github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= | ||||||
|  | github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= | ||||||
|  | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= | ||||||
|  | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= | ||||||
|  | github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= | ||||||
|  | github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= | ||||||
| github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= | ||||||
| github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= | ||||||
|  | github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI= | ||||||
|  | github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= | ||||||
|  | github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= | ||||||
|  | github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= | ||||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||||
| github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||||
| github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||||
| @@ -150,6 +183,10 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y | |||||||
| github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||||
| github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||||
| github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | ||||||
|  | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||||||
|  | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | ||||||
|  | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||||
|  | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||||
| github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | ||||||
| github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||||
| github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | ||||||
| @@ -158,8 +195,10 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a | |||||||
| github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||||
| github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||||
| github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
|  | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
|  | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
| github.com/google/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= | ||||||
| @@ -167,33 +206,41 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf | |||||||
| github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= | ||||||
| github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= | ||||||
| github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= | ||||||
| github.com/google/rpmpack v0.5.0 h1:L16KZ3QvkFGpYhmp23iQip+mx1X39foEsqszjMNBm8A= | github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE= | ||||||
| github.com/google/rpmpack v0.5.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= | github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= | ||||||
|  | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= | ||||||
|  | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= | ||||||
| github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
| github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | ||||||
| github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
| github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | ||||||
| github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | ||||||
| github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ= |  | ||||||
| github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM= |  | ||||||
| github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= | github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= | ||||||
| github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= | github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= | ||||||
| github.com/goreleaser/chglog v0.5.0 h1:Sk6BMIpx8+vpAf8KyPit34OgWui8c7nKTMHhYx88jJ4= | github.com/goreleaser/chglog v0.6.1 h1:NZKiX8l0FTQPRzBgKST7knvNZmZ04f7PEGkN2wInfhE= | ||||||
| github.com/goreleaser/chglog v0.5.0/go.mod h1:Ri46M3lrMuv76FHszs3vtABR8J8k1w9JHYAzxeeOl28= | github.com/goreleaser/chglog v0.6.1/go.mod h1:Bnnfo07jMZkaAb0uRNASMZyOsX6ROW6X1qbXqN3guUo= | ||||||
| github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I= | github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I= | ||||||
| github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU= | github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU= | ||||||
| github.com/goreleaser/nfpm/v2 v2.33.0 h1:yBv6jgkPwih4va/S42rceSjJ2Znt3Og/Ntc76oP0tfI= | github.com/goreleaser/nfpm/v2 v2.41.0 h1:JyMzS/EwqaWbFs+7Z9oZ4Hkk4or00gUTqwm9Dgr8QYg= | ||||||
| github.com/goreleaser/nfpm/v2 v2.33.0/go.mod h1:8wwWWvJWmn84xo/Sqiv0aMvEGTHlHZTXTEuVSgQpkIM= | github.com/goreleaser/nfpm/v2 v2.41.0/go.mod h1:VPc5kF5OgfA+BosV/A2aB+Vg34honjWvp0Vt8ogsSi0= | ||||||
| github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= | ||||||
| github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||||||
|  | github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= | ||||||
|  | github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= | ||||||
| github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= | ||||||
| github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= | ||||||
|  | github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= | ||||||
|  | github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= | ||||||
| github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||||
| github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||||
|  | github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= | ||||||
|  | github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= | ||||||
| github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= | ||||||
| github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | ||||||
| github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= | ||||||
| github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= | ||||||
|  | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= | ||||||
|  | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | ||||||
| github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= | github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= | ||||||
| github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= | github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= | ||||||
| github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||||
| @@ -202,24 +249,39 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= | |||||||
| github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= | ||||||
| github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= | ||||||
| github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= | ||||||
| github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= | github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc= | ||||||
| github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= | 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/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= | ||||||
|  | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | ||||||
|  | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | ||||||
| github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | ||||||
| github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= | ||||||
| github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | ||||||
| github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | ||||||
| github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= |  | ||||||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= | ||||||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= | ||||||
| github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= | ||||||
| github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= | ||||||
| github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||||||
| github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= | ||||||
| github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= | ||||||
| github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= | ||||||
| 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= | ||||||
| @@ -227,34 +289,34 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | |||||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||||
| github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= | github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8= | ||||||
| github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw= | ||||||
| github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | ||||||
| github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||||||
| github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= | github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= | ||||||
|  | github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= | ||||||
| github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= | ||||||
| github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= | ||||||
| github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= |  | ||||||
| github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||||||
|  | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||||||
|  | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= | ||||||
|  | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | ||||||
| github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | ||||||
| github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= | ||||||
| github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||||||
| github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||||||
|  | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||||
| github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= | ||||||
| github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= | ||||||
| github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||||||
| github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | ||||||
| github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||||
| github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= |  | ||||||
| github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= |  | ||||||
| github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= | github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= | ||||||
| github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= | github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= | ||||||
| github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= | ||||||
| github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= | ||||||
| github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= | github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= | ||||||
| github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A= | github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A= | ||||||
| github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= |  | ||||||
| github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= |  | ||||||
| github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= | ||||||
| github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= | ||||||
| github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= | ||||||
| @@ -263,8 +325,13 @@ 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/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||||
| github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= | ||||||
|  | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||||
|  | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= | ||||||
|  | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= | ||||||
|  | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= | ||||||
|  | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= | ||||||
| github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | ||||||
| github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= | ||||||
| github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= | ||||||
| @@ -273,14 +340,21 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo | |||||||
| github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= | github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= | ||||||
| github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk= | github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk= | ||||||
| github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= | github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= | ||||||
| github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= | github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= | ||||||
| github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= | ||||||
| github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||||
| github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= | ||||||
|  | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||||
|  | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | ||||||
|  | github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= | ||||||
|  | github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= | ||||||
|  | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= | ||||||
|  | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= | ||||||
| github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= | github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= | ||||||
| github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | ||||||
| github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= | github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= | ||||||
| github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= | github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= | ||||||
|  | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= | ||||||
| github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||||
| @@ -291,48 +365,58 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 | |||||||
| github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= | ||||||
| github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||||
| github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||||
| github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||||||
| github.com/rivo/uniseg v0.4.4/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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= | ||||||
| github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= | 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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= | ||||||
| github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||||
| github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= | github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= | ||||||
| github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE= | github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= | ||||||
| github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ= | github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= | ||||||
| github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= | ||||||
| github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= | 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.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= | github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= | ||||||
| github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= | github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= | ||||||
| github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU= | github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= | ||||||
| github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY= | github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= | ||||||
| github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w= | github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= | ||||||
| github.com/smartystreets/goconvey v1.8.0/go.mod h1:EdX8jtrTIj26jmjCOVNMVSIYAtgexqXKHOXW2Dx9JLg= | 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.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= | github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= | ||||||
| github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= | github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= | ||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= |  | ||||||
| github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= |  | ||||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||||
| github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
| github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
| github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||||
| github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
| github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= | ||||||
| github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= | ||||||
| github.com/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.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= | github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= | ||||||
| github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= | github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= | ||||||
| github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= | github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= | ||||||
| github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= | github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= | ||||||
| github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= | github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= | ||||||
| @@ -343,17 +427,17 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM | |||||||
| github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= | ||||||
| github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= | ||||||
| github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= | ||||||
| github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= |  | ||||||
| github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= |  | ||||||
| github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= | ||||||
| github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= | ||||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||||
| gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= | gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= | ||||||
| gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= | gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= | ||||||
| go.elara.ws/logger v0.0.0-20230421022458-e80700db2090 h1:RVC8XvWo6Yw4HUshqx4TSzuBDScDghafU6QFRJ4xPZg= | go.alt-gnome.ru/capytest v0.0.2 h1:clmvIqmYS86hhA1rsvivSSPpfOFkJTpbn38EQP7I3E8= | ||||||
| go.elara.ws/logger v0.0.0-20230421022458-e80700db2090/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM= | go.alt-gnome.ru/capytest v0.0.2/go.mod h1:lvxPx3H6h+LPnStBFblgoT2wkjv0wbug3S14troykEg= | ||||||
| go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6 h1:4xCBxLPBn3Y2DuIcj8zQ1tQOFLrpu6tEIGUWn/Q6zPM= | go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 h1:NST+V5LV/eLgs0p6PsuvfHiZ4UrIWqftCdifO8zgg0g= | ||||||
| go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6/go.mod h1:NmfCFqwq7X/aqa/ZVkIysj17JyMEY4Bb5E921kMswNo= | 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= | ||||||
| @@ -369,10 +453,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U | |||||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||||
| golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||||
| golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= | golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= | ||||||
| golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= | ||||||
| golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= | ||||||
| golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= |  | ||||||
| golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= |  | ||||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||||
| golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||||
| golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | ||||||
| @@ -381,8 +463,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE | |||||||
| golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||||||
| golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||||||
| golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | ||||||
| golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= | ||||||
| golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= | ||||||
| golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||||||
| golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||||
| @@ -401,11 +483,11 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= | |||||||
| golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||||||
| golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||||
| golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= | ||||||
| golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= | golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||||
| golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= |  | ||||||
| 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= | ||||||
| @@ -422,10 +504,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v | |||||||
| golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||||
| golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= | ||||||
| golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= | ||||||
| golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= | ||||||
| golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= |  | ||||||
| golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= |  | ||||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||||
| golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
| @@ -438,10 +518,10 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ | |||||||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= | ||||||
| golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | ||||||
| golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= |  | ||||||
| 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= | ||||||
| @@ -451,6 +531,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w | |||||||
| golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| @@ -458,27 +539,22 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w | |||||||
| golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |  | ||||||
| golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= | ||||||
| golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.33.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= | ||||||
| golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= | ||||||
| golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= | golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= | ||||||
| golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= |  | ||||||
| golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= |  | ||||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| @@ -487,10 +563,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | |||||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||||
| golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||||
| golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= | ||||||
| golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= | ||||||
| golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= |  | ||||||
| golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= |  | ||||||
| golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
| golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
| @@ -518,9 +592,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK | |||||||
| golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||||
| golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||||
| golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= | ||||||
| golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= | golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= | ||||||
| golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= |  | ||||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| @@ -550,6 +623,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-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= | ||||||
| google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | ||||||
| google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= | ||||||
| google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | ||||||
| @@ -557,17 +632,28 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac | |||||||
| google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||||||
| google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||||||
| google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||||||
|  | google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8= | ||||||
|  | google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s= | ||||||
|  | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||||
|  | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||||
|  | google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= | ||||||
|  | google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | ||||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||||||
| gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | ||||||
|  | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= | ||||||
|  | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | ||||||
|  | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= | ||||||
|  | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= | ||||||
| gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= | ||||||
| gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= | ||||||
|  | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |  | ||||||
| gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
|  | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||||||
| gopkg.in/yaml.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= | ||||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
| @@ -604,10 +690,12 @@ modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= | |||||||
| modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= | modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= | ||||||
| modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= | modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= | ||||||
| modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= | modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= | ||||||
| mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= | mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4= | ||||||
| mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= | mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY= | ||||||
| plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283 h1:BXCLPeA8g2M6qYngicyxyB/2Bo4J54Q9Rb+8jMmE3ik= |  | ||||||
| plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283/go.mod h1:itzL9Jx52VXOhRaucFHuMpa3y7iwjnuLGdNvypoh/S4= |  | ||||||
| rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= | ||||||
| rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= | ||||||
| rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= | ||||||
|  | xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM= | ||||||
|  | xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= | ||||||
|  | xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU= | ||||||
|  | xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw= | ||||||
|   | |||||||
							
								
								
									
										68
									
								
								helper.go
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								helper.go
									
									
									
									
									
								
							| @@ -1,35 +1,69 @@ | |||||||
|  | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  | // | ||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| 	"plemya-x.ru/alr/internal/cpu" |  | ||||||
| 	"plemya-x.ru/alr/internal/shutils/helpers" |  | ||||||
| 	"plemya-x.ru/alr/pkg/distro" |  | ||||||
| 	"plemya-x.ru/alr/pkg/loggerctx" |  | ||||||
| 	"mvdan.cc/sh/v3/expand" | 	"mvdan.cc/sh/v3/expand" | ||||||
| 	"mvdan.cc/sh/v3/interp" | 	"mvdan.cc/sh/v3/interp" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var helperCmd = &cli.Command{ | func HelperCmd() *cli.Command { | ||||||
|  | 	helperListCmd := &cli.Command{ | ||||||
|  | 		Name:    "list", | ||||||
|  | 		Usage:   gotext.Get("List all the available helper commands"), | ||||||
|  | 		Aliases: []string{"ls"}, | ||||||
|  | 		Action: func(ctx *cli.Context) error { | ||||||
|  | 			for name := range helpers.Helpers { | ||||||
|  | 				fmt.Println(name) | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &cli.Command{ | ||||||
| 		Name:        "helper", | 		Name:        "helper", | ||||||
| 	Usage:       "Run a ALR helper command", | 		Usage:       gotext.Get("Run a ALR helper command"), | ||||||
| 		ArgsUsage:   `<helper_name|"list">`, | 		ArgsUsage:   `<helper_name|"list">`, | ||||||
| 		Subcommands: []*cli.Command{helperListCmd}, | 		Subcommands: []*cli.Command{helperListCmd}, | ||||||
| 		Flags: []cli.Flag{ | 		Flags: []cli.Flag{ | ||||||
| 			&cli.StringFlag{ | 			&cli.StringFlag{ | ||||||
| 				Name:    "dest-dir", | 				Name:    "dest-dir", | ||||||
| 				Aliases: []string{"d"}, | 				Aliases: []string{"d"}, | ||||||
| 			Usage:   "The directory that the install commands will install to", | 				Usage:   gotext.Get("The directory that the install commands will install to"), | ||||||
| 				Value:   "dest", | 				Value:   "dest", | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
| 		log := loggerctx.From(ctx) |  | ||||||
|  |  | ||||||
| 			if c.Args().Len() < 1 { | 			if c.Args().Len() < 1 { | ||||||
| 				cli.ShowSubcommandHelpAndExit(c, 1) | 				cli.ShowSubcommandHelpAndExit(c, 1) | ||||||
| @@ -37,17 +71,18 @@ var helperCmd = &cli.Command{ | |||||||
|  |  | ||||||
| 			helper, ok := helpers.Helpers[c.Args().First()] | 			helper, ok := helpers.Helpers[c.Args().First()] | ||||||
| 			if !ok { | 			if !ok { | ||||||
| 			log.Fatal("No such helper command").Str("name", c.Args().First()).Send() | 				slog.Error(gotext.Get("No such helper command"), "name", c.Args().First()) | ||||||
|  | 				return cli.Exit(gotext.Get("No such helper command"), 1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			wd, err := os.Getwd() | 			wd, err := os.Getwd() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 			log.Fatal("Error getting working directory").Err(err).Send() | 				return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			info, err := distro.ParseOSRelease(ctx) | 			info, err := distro.ParseOSRelease(ctx) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 			log.Fatal("Error getting working directory").Err(err).Send() | 				return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			hc := interp.HandlerContext{ | 			hc := interp.HandlerContext{ | ||||||
| @@ -71,16 +106,5 @@ var helperCmd = &cli.Command{ | |||||||
| 				fmt.Println(name) | 				fmt.Println(name) | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| } |  | ||||||
|  |  | ||||||
| var helperListCmd = &cli.Command{ |  | ||||||
| 	Name:    "list", |  | ||||||
| 	Usage:   "List all the available helper commands", |  | ||||||
| 	Aliases: []string{"ls"}, |  | ||||||
| 	Action: func(ctx *cli.Context) error { |  | ||||||
| 		for name := range helpers.Helpers { |  | ||||||
| 			fmt.Println(name) |  | ||||||
| 	} | 	} | ||||||
| 		return nil |  | ||||||
| 	}, |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										143
									
								
								info.go
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								info.go
									
									
									
									
									
								
							| @@ -1,20 +1,21 @@ | |||||||
| /* | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  * ALR - Any Linux Repository | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  * Copyright (C) 2024 Евгений Храмов | // | ||||||
|  * | // ALR - Any Linux Repository | ||||||
|  * This program is free software: you can redistribute it and/or modify | // Copyright (C) 2025 The ALR Authors | ||||||
|  * it under the terms of the GNU General Public License as published by | // | ||||||
|  * the Free Software Foundation, either version 3 of the License, or | // This program is free software: you can redistribute it and/or modify | ||||||
|  * (at your option) any later version. | // it under the terms of the GNU General Public License as published by | ||||||
|  * | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  * This program is distributed in the hope that it will be useful, | // (at your option) any later version. | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | // | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | // This program is distributed in the hope that it will be useful, | ||||||
|  * GNU General Public License for more details. | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  * | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  * You should have received a copy of the GNU General Public License | // GNU General Public License for more details. | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | // | ||||||
|  */ | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package main | package main | ||||||
|  |  | ||||||
| @@ -22,47 +23,83 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
|  | 	"github.com/goccy/go-yaml" | ||||||
|  | 	"github.com/jeandeaual/go-locale" | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| 	"plemya-x.ru/alr/internal/cliutils" |  | ||||||
| 	"plemya-x.ru/alr/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	"plemya-x.ru/alr/internal/overrides" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"plemya-x.ru/alr/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | ||||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 	"plemya-x.ru/alr/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| 	"gopkg.in/yaml.v3" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var infoCmd = &cli.Command{ | func InfoCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
| 		Name:  "info", | 		Name:  "info", | ||||||
| 	Usage: "Print information about a package", | 		Usage: gotext.Get("Print information about a package"), | ||||||
| 		Flags: []cli.Flag{ | 		Flags: []cli.Flag{ | ||||||
| 			&cli.BoolFlag{ | 			&cli.BoolFlag{ | ||||||
| 				Name:    "all", | 				Name:    "all", | ||||||
| 				Aliases: []string{"a"}, | 				Aliases: []string{"a"}, | ||||||
| 			Usage:   "Show all information, not just for the current distro", | 				Usage:   gotext.Get("Show all information, not just for the current distro"), | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	Action: func(c *cli.Context) error { | 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||||
|  |  | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
| 		log := loggerctx.From(ctx) | 			deps, err := appbuilder. | ||||||
|  | 				New(ctx). | ||||||
|  | 				WithConfig(). | ||||||
|  | 				WithDB(). | ||||||
|  | 				Build() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			result, err := deps.DB.GetPkgs(c.Context, "true") | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for _, pkg := range result { | ||||||
|  | 				fmt.Println(pkg.Name) | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		}), | ||||||
|  | 		Action: func(c *cli.Context) error { | ||||||
|  | 			// Запуск от текущего пользователя | ||||||
|  |  | ||||||
| 			args := c.Args() | 			args := c.Args() | ||||||
| 			if args.Len() < 1 { | 			if args.Len() < 1 { | ||||||
| 			log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send() | 				return cli.Exit(gotext.Get("Command info expected at least 1 argument, got %d", args.Len()), 1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 		err := repos.Pull(ctx, config.Config(ctx).Repos) | 			ctx := c.Context | ||||||
| 		if err != nil { |  | ||||||
| 			log.Fatal("Error pulling repositories").Err(err).Send() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		found, _, err := repos.FindPkgs(ctx, args.Slice()) | 			deps, err := appbuilder. | ||||||
|  | 				New(ctx). | ||||||
|  | 				WithConfig(). | ||||||
|  | 				WithDB(). | ||||||
|  | 				WithDistroInfo(). | ||||||
|  | 				WithRepos(). | ||||||
|  | 				Build() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 			log.Fatal("Error finding packages").Err(err).Send() | 				return cli.Exit(err, 1) | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			rs := deps.Repos | ||||||
|  |  | ||||||
|  | 			found, _, err := rs.FindPkgs(ctx, args.Slice()) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Error finding packages"), err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if len(found) == 0 { | 			if len(found) == 0 { | ||||||
| 			os.Exit(1) | 				return cliutils.FormatCliExit(gotext.Get("Package not found"), err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive")) | 			pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive")) | ||||||
| @@ -70,37 +107,39 @@ var infoCmd = &cli.Command{ | |||||||
| 			var names []string | 			var names []string | ||||||
| 			all := c.Bool("all") | 			all := c.Bool("all") | ||||||
|  |  | ||||||
| 		if !all { | 			systemLang, err := locale.GetLanguage() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Can't detect system language"), err) | ||||||
|  | 			} | ||||||
|  | 			if systemLang == "" { | ||||||
|  | 				systemLang = "en" | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			info, err := distro.ParseOSRelease(ctx) | 			info, err := distro.ParseOSRelease(ctx) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				log.Fatal("Error parsing os-release file").Err(err).Send() | 				return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) | ||||||
| 			} | 			} | ||||||
| 			names, err = overrides.Resolve( | 			names, err = overrides.Resolve( | ||||||
| 				info, | 				info, | ||||||
| 				overrides.DefaultOpts. | 				overrides.DefaultOpts. | ||||||
| 					WithLanguages([]string{config.SystemLang()}), | 					WithLanguages([]string{systemLang}), | ||||||
| 			) | 			) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				log.Fatal("Error resolving overrides").Err(err).Send() | 				return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err) | ||||||
| 			} |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			for _, pkg := range pkgs { | 			for _, pkg := range pkgs { | ||||||
| 			if !all { | 				alrsh.ResolvePackage(&pkg, names) | ||||||
| 				err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names)) | 				view := alrsh.NewPackageView(pkg) | ||||||
|  | 				view.Resolved = !all | ||||||
|  | 				err = yaml.NewEncoder(os.Stdout, yaml.UseJSONMarshaler(), yaml.OmitEmpty()).Encode(view) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					log.Fatal("Error encoding script variables").Err(err).Send() | 					return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) | ||||||
| 				} | 				} | ||||||
| 			} else { |  | ||||||
| 				err = yaml.NewEncoder(os.Stdout).Encode(pkg) |  | ||||||
| 				if err != nil { |  | ||||||
| 					log.Fatal("Error encoding script variables").Err(err).Send() |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 				fmt.Println("---") | 				fmt.Println("---") | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			return nil | 			return nil | ||||||
| 		}, | 		}, | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										241
									
								
								install.go
									
									
									
									
									
								
							
							
						
						
									
										241
									
								
								install.go
									
									
									
									
									
								
							| @@ -1,122 +1,211 @@ | |||||||
| /* | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  * ALR - Any Linux Repository | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  * Copyright (C) 2024 Евгений Храмов | // | ||||||
|  * | // ALR - Any Linux Repository | ||||||
|  * This program is free software: you can redistribute it and/or modify | // Copyright (C) 2025 The ALR Authors | ||||||
|  * it under the terms of the GNU General Public License as published by | // | ||||||
|  * the Free Software Foundation, either version 3 of the License, or | // This program is free software: you can redistribute it and/or modify | ||||||
|  * (at your option) any later version. | // it under the terms of the GNU General Public License as published by | ||||||
|  * | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  * This program is distributed in the hope that it will be useful, | // (at your option) any later version. | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | // | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | // This program is distributed in the hope that it will be useful, | ||||||
|  * GNU General Public License for more details. | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  * | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  * You should have received a copy of the GNU General Public License | // GNU General Public License for more details. | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | // | ||||||
|  */ | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| 	"plemya-x.ru/alr/internal/cliutils" |  | ||||||
| 	"plemya-x.ru/alr/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" | ||||||
| 	"plemya-x.ru/alr/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	"plemya-x.ru/alr/internal/types" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"plemya-x.ru/alr/pkg/build" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| 	"plemya-x.ru/alr/pkg/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| 	"plemya-x.ru/alr/pkg/repos" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var installCmd = &cli.Command{ | func InstallCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
| 		Name:    "install", | 		Name:    "install", | ||||||
| 	Usage:   "Install a new package", | 		Usage:   gotext.Get("Install a new package"), | ||||||
| 		Aliases: []string{"in"}, | 		Aliases: []string{"in"}, | ||||||
| 		Flags: []cli.Flag{ | 		Flags: []cli.Flag{ | ||||||
| 			&cli.BoolFlag{ | 			&cli.BoolFlag{ | ||||||
| 				Name:    "clean", | 				Name:    "clean", | ||||||
| 				Aliases: []string{"c"}, | 				Aliases: []string{"c"}, | ||||||
| 			Usage:   "Build package from scratch even if there's an already built package available", | 				Usage:   gotext.Get("Build package from scratch even if there's an already built package available"), | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	Action: func(c *cli.Context) error { | 		Action: utils.RootNeededAction(func(c *cli.Context) error { | ||||||
| 		ctx := c.Context |  | ||||||
| 		log := loggerctx.From(ctx) |  | ||||||
|  |  | ||||||
| 			args := c.Args() | 			args := c.Args() | ||||||
| 			if args.Len() < 1 { | 			if args.Len() < 1 { | ||||||
| 			log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send() | 				return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 		mgr := manager.Detect() |  | ||||||
| 		if mgr == nil { |  | ||||||
| 			log.Fatal("Unable to detect a supported package manager on the system").Send() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		err := repos.Pull(ctx, config.Config(ctx).Repos) | 			installer, installerClose, err := build.GetSafeInstaller() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 			log.Fatal("Error pulling repositories").Err(err).Send() | 				return err | ||||||
| 			} | 			} | ||||||
|  | 			defer installerClose() | ||||||
|  |  | ||||||
| 		found, notFound, err := repos.FindPkgs(ctx, args.Slice()) |  | ||||||
|  | 			scripter, scripterClose, err := build.GetSafeScriptExecutor() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 			log.Fatal("Error finding packages").Err(err).Send() | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer scripterClose() | ||||||
|  |  | ||||||
|  | 			ctx := c.Context | ||||||
|  |  | ||||||
|  | 			deps, err := appbuilder. | ||||||
|  | 				New(ctx). | ||||||
|  | 				WithConfig(). | ||||||
|  | 				WithDB(). | ||||||
|  | 				WithRepos(). | ||||||
|  | 				WithDistroInfo(). | ||||||
|  | 				WithManager(). | ||||||
|  | 				Build() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			builder, err := build.NewMainBuilder( | ||||||
|  | 				deps.Cfg, | ||||||
|  | 				deps.Manager, | ||||||
|  | 				deps.Repos, | ||||||
|  | 				scripter, | ||||||
|  | 				installer, | ||||||
|  | 			) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 		pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive")) | 			_, err = builder.InstallPkgs( | ||||||
| 		build.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{ | 				ctx, | ||||||
| 			Manager:     mgr, | 				&build.BuildArgs{ | ||||||
|  | 					Opts: &types.BuildOpts{ | ||||||
| 						Clean:       c.Bool("clean"), | 						Clean:       c.Bool("clean"), | ||||||
| 						Interactive: c.Bool("interactive"), | 						Interactive: c.Bool("interactive"), | ||||||
| 		}) |  | ||||||
| 		return nil |  | ||||||
| 					}, | 					}, | ||||||
| 	BashComplete: func(c *cli.Context) { | 					Info:       deps.Info, | ||||||
| 		log := loggerctx.From(c.Context) | 					PkgFormat_: build.GetPkgFormat(deps.Manager), | ||||||
| 		result, err := db.GetPkgs(c.Context, "true") | 				}, | ||||||
|  | 				args.Slice(), | ||||||
|  | 			) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 			log.Fatal("Error getting packages").Err(err).Send() | 				return cliutils.FormatCliExit(gotext.Get("Error when installing the package"), err) | ||||||
| 		} |  | ||||||
| 		defer result.Close() |  | ||||||
|  |  | ||||||
| 		for result.Next() { |  | ||||||
| 			var pkg db.Package |  | ||||||
| 			err = result.StructScan(&pkg) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Fatal("Error iterating over packages").Err(err).Send() |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			return nil | ||||||
|  | 		}), | ||||||
|  | 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||||
|  |  | ||||||
|  | 			ctx := c.Context | ||||||
|  | 			deps, err := appbuilder. | ||||||
|  | 				New(ctx). | ||||||
|  | 				WithConfig(). | ||||||
|  | 				WithDB(). | ||||||
|  | 				Build() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			result, err := deps.DB.GetPkgs(c.Context, "true") | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for _, pkg := range result { | ||||||
| 				fmt.Println(pkg.Name) | 				fmt.Println(pkg.Name) | ||||||
| 			} | 			} | ||||||
| 	}, |  | ||||||
|  | 			return nil | ||||||
|  | 		}), | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| var removeCmd = &cli.Command{ | func RemoveCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
| 		Name:    "remove", | 		Name:    "remove", | ||||||
| 	Usage:   "Remove an installed package", | 		Usage:   gotext.Get("Remove an installed package"), | ||||||
| 		Aliases: []string{"rm"}, | 		Aliases: []string{"rm"}, | ||||||
| 	Action: func(c *cli.Context) error { | 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||||
| 		log := loggerctx.From(c.Context) | 			ctx := c.Context | ||||||
|  |  | ||||||
| 		args := c.Args() | 			deps, err := appbuilder. | ||||||
| 		if args.Len() < 1 { | 				New(ctx). | ||||||
| 			log.Fatalf("Command remove expected at least 1 argument, got %d", args.Len()).Send() | 				WithConfig(). | ||||||
| 		} | 				WithDB(). | ||||||
|  | 				WithManager(). | ||||||
| 		mgr := manager.Detect() | 				Build() | ||||||
| 		if mgr == nil { |  | ||||||
| 			log.Fatal("Unable to detect a supported package manager on the system").Send() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		err := mgr.Remove(nil, c.Args().Slice()...) |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 			log.Fatal("Error removing packages").Err(err).Send() | 				return cli.Exit(err, 1) | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			installedAlrPackages := map[string]string{} | ||||||
|  | 			installed, err := deps.Manager.ListInstalled(&manager.Opts{}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Error listing installed packages"), err) | ||||||
|  | 			} | ||||||
|  | 			for pkgName, version := range installed { | ||||||
|  | 				matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName) | ||||||
|  | 				if matches != nil { | ||||||
|  | 					packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")] | ||||||
|  | 					repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")] | ||||||
|  | 					installedAlrPackages[fmt.Sprintf("%s/%s", repoName, packageName)] = version | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			result, err := deps.DB.GetPkgs(c.Context, "true") | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for _, pkg := range result { | ||||||
|  | 				_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] | ||||||
|  | 				if !ok { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 				fmt.Println(pkg.Name) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			return nil | 			return nil | ||||||
| 	}, | 		}), | ||||||
|  | 		Action: utils.RootNeededAction(func(c *cli.Context) error { | ||||||
|  | 			args := c.Args() | ||||||
|  | 			if args.Len() < 1 { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len()), nil) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			deps, err := appbuilder. | ||||||
|  | 				New(c.Context). | ||||||
|  | 				WithManager(). | ||||||
|  | 				Build() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			if err := deps.Manager.Remove(&manager.Opts{ | ||||||
|  | 				NoConfirm: !c.Bool("interactive"), | ||||||
|  | 			}, c.Args().Slice()...); err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Error removing packages"), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return nil | ||||||
|  | 		}), | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										156
									
								
								internal.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								internal.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 	"syscall" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/go-hclog" | ||||||
|  | 	"github.com/hashicorp/go-plugin" | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
|  | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func InternalBuildCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
|  | 		Name:     "_internal-safe-script-executor", | ||||||
|  | 		HideHelp: true, | ||||||
|  | 		Hidden:   true, | ||||||
|  | 		Action: func(c *cli.Context) error { | ||||||
|  | 			logger.SetupForGoPlugin() | ||||||
|  |  | ||||||
|  | 			slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 			cfg := config.New() | ||||||
|  | 			err := cfg.Load() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("Error loading config"), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			logger := hclog.New(&hclog.LoggerOptions{ | ||||||
|  | 				Name:        "plugin", | ||||||
|  | 				Output:      os.Stderr, | ||||||
|  | 				Level:       hclog.Debug, | ||||||
|  | 				JSONFormat:  false, | ||||||
|  | 				DisableTime: true, | ||||||
|  | 			}) | ||||||
|  |  | ||||||
|  | 			plugin.Serve(&plugin.ServeConfig{ | ||||||
|  | 				HandshakeConfig: build.HandshakeConfig, | ||||||
|  | 				Plugins: map[string]plugin.Plugin{ | ||||||
|  | 					"script-executor": &build.ScriptExecutorPlugin{ | ||||||
|  | 						Impl: build.NewLocalScriptExecutor(cfg), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				Logger: logger, | ||||||
|  | 			}) | ||||||
|  | 			return nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func InternalReposCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
|  | 		Name:     "_internal-repos", | ||||||
|  | 		HideHelp: true, | ||||||
|  | 		Hidden:   true, | ||||||
|  | 		Action: utils.RootNeededAction(func(ctx *cli.Context) error { | ||||||
|  | 			logger.SetupForGoPlugin() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 			deps, err := appbuilder. | ||||||
|  | 				New(ctx.Context). | ||||||
|  | 				WithConfig(). | ||||||
|  | 				WithDB(). | ||||||
|  | 				WithReposNoPull(). | ||||||
|  | 				Build() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			pluginCfg := build.GetPluginServeCommonConfig() | ||||||
|  | 			pluginCfg.Plugins = map[string]plugin.Plugin{ | ||||||
|  | 				"repos": &build.ReposExecutorPlugin{ | ||||||
|  | 					Impl: build.NewRepos( | ||||||
|  | 						deps.Repos, | ||||||
|  | 					), | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 			plugin.Serve(pluginCfg) | ||||||
|  | 			return nil | ||||||
|  | 		}), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func InternalInstallCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
|  | 		Name:     "_internal-installer", | ||||||
|  | 		HideHelp: true, | ||||||
|  | 		Hidden:   true, | ||||||
|  | 		Action: func(c *cli.Context) error { | ||||||
|  | 			logger.SetupForGoPlugin() | ||||||
|  |  | ||||||
|  | 			// Запуск от текущего пользователя, повышение прав будет через sudo при необходимости | ||||||
|  |  | ||||||
|  | 			deps, err := appbuilder. | ||||||
|  | 				New(c.Context). | ||||||
|  | 				WithConfig(). | ||||||
|  | 				WithDB(). | ||||||
|  | 				WithReposNoPull(). | ||||||
|  | 				Build() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			logger := hclog.New(&hclog.LoggerOptions{ | ||||||
|  | 				Name:        "plugin", | ||||||
|  | 				Output:      os.Stderr, | ||||||
|  | 				Level:       hclog.Trace, | ||||||
|  | 				JSONFormat:  true, | ||||||
|  | 				DisableTime: true, | ||||||
|  | 			}) | ||||||
|  |  | ||||||
|  | 			plugin.Serve(&plugin.ServeConfig{ | ||||||
|  | 				HandshakeConfig: build.HandshakeConfig, | ||||||
|  | 				Plugins: map[string]plugin.Plugin{ | ||||||
|  | 					"installer": &build.InstallerExecutorPlugin{ | ||||||
|  | 						Impl: build.NewInstaller( | ||||||
|  | 							manager.Detect(), | ||||||
|  | 						), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				Logger: logger, | ||||||
|  | 			}) | ||||||
|  | 			return nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										745
									
								
								internal/build/build.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										745
									
								
								internal/build/build.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,745 @@ | |||||||
|  | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  | // | ||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/gob" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log/slog" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/stats" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type BuildInput struct { | ||||||
|  | 	opts       *types.BuildOpts | ||||||
|  | 	info       *distro.OSRelease | ||||||
|  | 	pkgFormat  string | ||||||
|  | 	script     string | ||||||
|  | 	repository string | ||||||
|  | 	packages   []string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (bi *BuildInput) GobEncode() ([]byte, error) { | ||||||
|  | 	w := new(bytes.Buffer) | ||||||
|  | 	encoder := gob.NewEncoder(w) | ||||||
|  |  | ||||||
|  | 	if err := encoder.Encode(bi.opts); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if err := encoder.Encode(bi.info); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if err := encoder.Encode(bi.pkgFormat); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if err := encoder.Encode(bi.script); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if err := encoder.Encode(bi.repository); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if err := encoder.Encode(bi.packages); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return w.Bytes(), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (bi *BuildInput) GobDecode(data []byte) error { | ||||||
|  | 	r := bytes.NewBuffer(data) | ||||||
|  | 	decoder := gob.NewDecoder(r) | ||||||
|  |  | ||||||
|  | 	if err := decoder.Decode(&bi.opts); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := decoder.Decode(&bi.info); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := decoder.Decode(&bi.pkgFormat); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := decoder.Decode(&bi.script); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := decoder.Decode(&bi.repository); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := decoder.Decode(&bi.packages); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *BuildInput) Repository() string { | ||||||
|  | 	return b.repository | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *BuildInput) BuildOpts() *types.BuildOpts { | ||||||
|  | 	return b.opts | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *BuildInput) OSRelease() *distro.OSRelease { | ||||||
|  | 	return b.info | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *BuildInput) PkgFormat() string { | ||||||
|  | 	return b.pkgFormat | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type BuildOptsProvider interface { | ||||||
|  | 	BuildOpts() *types.BuildOpts | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type OsInfoProvider interface { | ||||||
|  | 	OSRelease() *distro.OSRelease | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type PkgFormatProvider interface { | ||||||
|  | 	PkgFormat() string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RepositoryProvider interface { | ||||||
|  | 	Repository() string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ================================================ | ||||||
|  |  | ||||||
|  | type BuiltDep struct { | ||||||
|  | 	Name string | ||||||
|  | 	Path string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func Map[T, R any](items []T, f func(T) R) []R { | ||||||
|  | 	res := make([]R, len(items)) | ||||||
|  | 	for i, item := range items { | ||||||
|  | 		res[i] = f(item) | ||||||
|  | 	} | ||||||
|  | 	return res | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetBuiltPaths(deps []*BuiltDep) []string { | ||||||
|  | 	return Map(deps, func(dep *BuiltDep) string { | ||||||
|  | 		return dep.Path | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetBuiltName(deps []*BuiltDep) []string { | ||||||
|  | 	return Map(deps, func(dep *BuiltDep) string { | ||||||
|  | 		return dep.Name | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type PackageFinder interface { | ||||||
|  | 	FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Config interface { | ||||||
|  | 	GetPaths() *config.Paths | ||||||
|  | 	PagerStyle() string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type FunctionsOutput struct { | ||||||
|  | 	Contents *[]string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EXECUTORS | ||||||
|  |  | ||||||
|  | type ScriptResolverExecutor interface { | ||||||
|  | 	ResolveScript(ctx context.Context, pkg *alrsh.Package) *ScriptInfo | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type CacheExecutor interface { | ||||||
|  | 	CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *alrsh.Package) (string, bool, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ScriptViewerExecutor interface { | ||||||
|  | 	ViewScript(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile, basePkg string) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type CheckerExecutor interface { | ||||||
|  | 	PerformChecks( | ||||||
|  | 		ctx context.Context, | ||||||
|  | 		input *BuildInput, | ||||||
|  | 		vars *alrsh.Package, | ||||||
|  | 	) (bool, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type SourcesInput struct { | ||||||
|  | 	Sources   []string | ||||||
|  | 	Checksums []string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type SourceDownloaderExecutor interface { | ||||||
|  | 	DownloadSources( | ||||||
|  | 		ctx context.Context, | ||||||
|  | 		input *BuildInput, | ||||||
|  | 		basePkg string, | ||||||
|  | 		si SourcesInput, | ||||||
|  | 	) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // | ||||||
|  |  | ||||||
|  | func NewBuilder( | ||||||
|  | 	scriptResolver ScriptResolverExecutor, | ||||||
|  | 	scriptExecutor ScriptExecutor, | ||||||
|  | 	cacheExecutor CacheExecutor, | ||||||
|  | 	scriptViewerExecutor ScriptViewerExecutor, | ||||||
|  | 	checkerExecutor CheckerExecutor, | ||||||
|  | 	installerExecutor InstallerExecutor, | ||||||
|  | 	sourceExecutor SourceDownloaderExecutor, | ||||||
|  | ) *Builder { | ||||||
|  | 	return &Builder{ | ||||||
|  | 		scriptResolver:       scriptResolver, | ||||||
|  | 		scriptExecutor:       scriptExecutor, | ||||||
|  | 		cacheExecutor:        cacheExecutor, | ||||||
|  | 		scriptViewerExecutor: scriptViewerExecutor, | ||||||
|  | 		checkerExecutor:      checkerExecutor, | ||||||
|  | 		installerExecutor:    installerExecutor, | ||||||
|  | 		sourceExecutor:       sourceExecutor, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Builder struct { | ||||||
|  | 	scriptResolver       ScriptResolverExecutor | ||||||
|  | 	scriptExecutor       ScriptExecutor | ||||||
|  | 	cacheExecutor        CacheExecutor | ||||||
|  | 	scriptViewerExecutor ScriptViewerExecutor | ||||||
|  | 	checkerExecutor      CheckerExecutor | ||||||
|  | 	installerExecutor    InstallerExecutor | ||||||
|  | 	sourceExecutor       SourceDownloaderExecutor | ||||||
|  | 	repos                PackageFinder | ||||||
|  | 	// mgr                  manager.Manager | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type BuildArgs struct { | ||||||
|  | 	Opts       *types.BuildOpts | ||||||
|  | 	Info       *distro.OSRelease | ||||||
|  | 	PkgFormat_ string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *BuildArgs) BuildOpts() *types.BuildOpts { | ||||||
|  | 	return b.Opts | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *BuildArgs) OSRelease() *distro.OSRelease { | ||||||
|  | 	return b.Info | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *BuildArgs) PkgFormat() string { | ||||||
|  | 	return b.PkgFormat_ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type BuildPackageFromDbArgs struct { | ||||||
|  | 	BuildArgs | ||||||
|  | 	Package  *alrsh.Package | ||||||
|  | 	Packages []string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type BuildPackageFromScriptArgs struct { | ||||||
|  | 	BuildArgs | ||||||
|  | 	Script   string | ||||||
|  | 	Packages []string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Builder) BuildPackageFromDb( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	args *BuildPackageFromDbArgs, | ||||||
|  | ) ([]*BuiltDep, error) { | ||||||
|  | 	scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package) | ||||||
|  |  | ||||||
|  | 	return b.BuildPackage(ctx, &BuildInput{ | ||||||
|  | 		script:     scriptInfo.Script, | ||||||
|  | 		repository: scriptInfo.Repository, | ||||||
|  | 		packages:   args.Packages, | ||||||
|  | 		pkgFormat:  args.PkgFormat(), | ||||||
|  | 		opts:       args.Opts, | ||||||
|  | 		info:       args.Info, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Builder) BuildPackageFromScript( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	args *BuildPackageFromScriptArgs, | ||||||
|  | ) ([]*BuiltDep, error) { | ||||||
|  | 	return b.BuildPackage(ctx, &BuildInput{ | ||||||
|  | 		script:     args.Script, | ||||||
|  | 		repository: "default", | ||||||
|  | 		packages:   args.Packages, | ||||||
|  | 		pkgFormat:  args.PkgFormat(), | ||||||
|  | 		opts:       args.Opts, | ||||||
|  | 		info:       args.Info, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Builder) BuildPackage( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input *BuildInput, | ||||||
|  | ) ([]*BuiltDep, error) { | ||||||
|  | 	scriptPath := input.script | ||||||
|  |  | ||||||
|  | 	slog.Debug("ReadScript") | ||||||
|  | 	sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed reading script: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Debug("ExecuteFirstPass") | ||||||
|  | 	basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed ExecuteFirstPass: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var builtDeps []*BuiltDep | ||||||
|  | 	var remainingVars []*alrsh.Package | ||||||
|  |  | ||||||
|  | 	if !input.opts.Clean { | ||||||
|  | 		for _, vars := range varsOfPackages { | ||||||
|  | 			builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			if ok { | ||||||
|  | 				builtDeps = append(builtDeps, &BuiltDep{ | ||||||
|  | 					Path: builtPkgPath, | ||||||
|  | 					Name: vars.Name, | ||||||
|  | 				}) | ||||||
|  | 			} else { | ||||||
|  | 				remainingVars = append(remainingVars, vars) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if len(remainingVars) == 0 { | ||||||
|  | 			slog.Info(gotext.Get("Using cached package"), "name", basePkg) | ||||||
|  | 			return builtDeps, nil | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		// Обновляем varsOfPackages только теми пакетами, которые нужно собрать | ||||||
|  | 		varsOfPackages = remainingVars | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Debug("ViewScript") | ||||||
|  | 	slog.Debug("", "varsOfPackages", varsOfPackages[0]) | ||||||
|  | 	err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Info(gotext.Get("Building package"), "name", basePkg) | ||||||
|  |  | ||||||
|  | 	for _, vars := range varsOfPackages { | ||||||
|  | 		cont, err := b.checkerExecutor.PerformChecks(ctx, input, vars) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if !cont { | ||||||
|  | 			return nil, errors.New("exit...") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	buildDepends := []string{} | ||||||
|  | 	optDepends := []string{} | ||||||
|  | 	depends := []string{} | ||||||
|  | 	sources := []string{} | ||||||
|  | 	checksums := []string{} | ||||||
|  | 	for _, vars := range varsOfPackages { | ||||||
|  | 		buildDepends = append(buildDepends, vars.BuildDepends.Resolved()...) | ||||||
|  | 		optDepends = append(optDepends, vars.OptDepends.Resolved()...) | ||||||
|  | 		depends = append(depends, vars.Depends.Resolved()...) | ||||||
|  | 		sources = append(sources, vars.Sources.Resolved()...) | ||||||
|  | 		checksums = append(checksums, vars.Checksums.Resolved()...) | ||||||
|  | 	} | ||||||
|  | 	buildDepends = removeDuplicates(buildDepends) | ||||||
|  | 	optDepends = removeDuplicates(optDepends) | ||||||
|  | 	depends = removeDuplicates(depends) | ||||||
|  |  | ||||||
|  | 	if len(sources) != len(checksums) { | ||||||
|  | 		slog.Error(gotext.Get("The checksums array must be the same length as sources")) | ||||||
|  | 		return nil, errors.New("exit...") | ||||||
|  | 	} | ||||||
|  | 	sources, checksums = removeDuplicatesSources(sources, checksums) | ||||||
|  |  | ||||||
|  | 	slog.Debug("installBuildDeps") | ||||||
|  | 	alrBuildDeps, installedBuildDeps, err := b.installBuildDeps(ctx, input, buildDepends) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Debug("installOptDeps") | ||||||
|  | 	_, err = b.installOptDeps(ctx, input, optDepends) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	depNames := make(map[string]struct{}) | ||||||
|  | 	for _, dep := range alrBuildDeps { | ||||||
|  | 		depNames[dep.Name] = struct{}{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// We filter so as not to re-build what has already been built at the `installBuildDeps` stage. | ||||||
|  | 	var filteredDepends []string | ||||||
|  | 	 | ||||||
|  | 	// Создаем набор подпакетов текущего мультипакета для исключения циклических зависимостей | ||||||
|  | 	currentPackageNames := make(map[string]struct{}) | ||||||
|  | 	for _, pkg := range input.packages { | ||||||
|  | 		currentPackageNames[pkg] = struct{}{} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	for _, d := range depends { | ||||||
|  | 		if _, found := depNames[d]; !found { | ||||||
|  | 			// Исключаем зависимости, которые являются подпакетами текущего мультипакета | ||||||
|  | 			if _, isCurrentPackage := currentPackageNames[d]; !isCurrentPackage { | ||||||
|  | 				filteredDepends = append(filteredDepends, d) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Debug("BuildALRDeps") | ||||||
|  | 	newBuiltDeps, repoDeps, err := b.BuildALRDeps(ctx, input, filteredDepends) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Debug("PrepareDirs") | ||||||
|  | 	err = b.scriptExecutor.PrepareDirs(ctx, input, basePkg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Info(gotext.Get("Downloading sources")) | ||||||
|  | 	slog.Debug("DownloadSources") | ||||||
|  | 	err = b.sourceExecutor.DownloadSources( | ||||||
|  | 		ctx, | ||||||
|  | 		input, | ||||||
|  | 		basePkg, | ||||||
|  | 		SourcesInput{ | ||||||
|  | 			Sources:   sources, | ||||||
|  | 			Checksums: checksums, | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	builtDeps = removeDuplicates(append(builtDeps, newBuiltDeps...)) | ||||||
|  |  | ||||||
|  | 	slog.Debug("ExecuteSecondPass") | ||||||
|  | 	res, err := b.scriptExecutor.ExecuteSecondPass( | ||||||
|  | 		ctx, | ||||||
|  | 		input, | ||||||
|  | 		sf, | ||||||
|  | 		varsOfPackages, | ||||||
|  | 		repoDeps, | ||||||
|  | 		builtDeps, | ||||||
|  | 		basePkg, | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	builtDeps = removeDuplicates(append(builtDeps, res...)) | ||||||
|  |  | ||||||
|  | 	err = b.removeBuildDeps(ctx, input, installedBuildDeps) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return builtDeps, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Builder) removeBuildDeps(ctx context.Context, input interface { | ||||||
|  | 	BuildOptsProvider | ||||||
|  | }, deps []string, | ||||||
|  | ) error { | ||||||
|  | 	if len(deps) > 0 { | ||||||
|  | 		remove, err := cliutils.YesNoPrompt(ctx, gotext.Get("Would you like to remove the build dependencies?"), input.BuildOpts().Interactive, false) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if remove { | ||||||
|  | 			err = b.installerExecutor.Remove( | ||||||
|  | 				ctx, | ||||||
|  | 				deps, | ||||||
|  | 				&manager.Opts{ | ||||||
|  | 					NoConfirm: !input.BuildOpts().Interactive, | ||||||
|  | 				}, | ||||||
|  | 			) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type InstallPkgsArgs struct { | ||||||
|  | 	BuildArgs | ||||||
|  | 	AlrPkgs    []alrsh.Package | ||||||
|  | 	NativePkgs []string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Builder) InstallALRPackages( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input interface { | ||||||
|  | 		OsInfoProvider | ||||||
|  | 		BuildOptsProvider | ||||||
|  | 		PkgFormatProvider | ||||||
|  | 	}, | ||||||
|  | 	alrPkgs []alrsh.Package, | ||||||
|  | ) error { | ||||||
|  | 	for _, pkg := range alrPkgs { | ||||||
|  | 		res, err := b.BuildPackageFromDb( | ||||||
|  | 			ctx, | ||||||
|  | 			&BuildPackageFromDbArgs{ | ||||||
|  | 				Package:  &pkg, | ||||||
|  | 				Packages: []string{}, | ||||||
|  | 				BuildArgs: BuildArgs{ | ||||||
|  | 					Opts:       input.BuildOpts(), | ||||||
|  | 					Info:       input.OSRelease(), | ||||||
|  | 					PkgFormat_: input.PkgFormat(), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		err = b.installerExecutor.InstallLocal( | ||||||
|  | 			ctx, | ||||||
|  | 			GetBuiltPaths(res), | ||||||
|  | 			&manager.Opts{ | ||||||
|  | 				NoConfirm: !input.BuildOpts().Interactive, | ||||||
|  | 			}, | ||||||
|  | 		) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		// Отслеживание установки ALR пакетов | ||||||
|  | 		for _, dep := range res { | ||||||
|  | 			if stats.ShouldTrackPackage(dep.Name) { | ||||||
|  | 				stats.TrackInstallation(ctx, dep.Name, "upgrade") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Builder) BuildALRDeps( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input interface { | ||||||
|  | 		OsInfoProvider | ||||||
|  | 		BuildOptsProvider | ||||||
|  | 		PkgFormatProvider | ||||||
|  | 	}, | ||||||
|  | 	depends []string, | ||||||
|  | ) (buildDeps []*BuiltDep, repoDeps []string, err error) { | ||||||
|  | 	if len(depends) > 0 { | ||||||
|  | 		slog.Info(gotext.Get("Installing dependencies")) | ||||||
|  |  | ||||||
|  | 		found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, nil, fmt.Errorf("failed FindPkgs: %w", err) | ||||||
|  | 		} | ||||||
|  | 		repoDeps = notFound | ||||||
|  |  | ||||||
|  | 		// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез | ||||||
|  | 		// Для зависимостей указываем isDependency = true | ||||||
|  | 		pkgs := cliutils.FlattenPkgsWithContext( | ||||||
|  | 			ctx, | ||||||
|  | 			found, | ||||||
|  | 			"install", | ||||||
|  | 			input.BuildOpts().Interactive, | ||||||
|  | 			true, | ||||||
|  | 		) | ||||||
|  | 		type item struct { | ||||||
|  | 			pkg      *alrsh.Package | ||||||
|  | 			packages []string | ||||||
|  | 		} | ||||||
|  | 		pkgsMap := make(map[string]*item) | ||||||
|  | 		for _, pkg := range pkgs { | ||||||
|  | 			name := pkg.BasePkgName | ||||||
|  | 			if name == "" { | ||||||
|  | 				name = pkg.Name | ||||||
|  | 			} | ||||||
|  | 			if pkgsMap[name] == nil { | ||||||
|  | 				pkgsMap[name] = &item{ | ||||||
|  | 					pkg: &pkg, | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			pkgsMap[name].packages = append( | ||||||
|  | 				pkgsMap[name].packages, | ||||||
|  | 				pkg.Name, | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for basePkgName := range pkgsMap { | ||||||
|  | 			pkg := pkgsMap[basePkgName].pkg | ||||||
|  | 			res, err := b.BuildPackageFromDb( | ||||||
|  | 				ctx, | ||||||
|  | 				&BuildPackageFromDbArgs{ | ||||||
|  | 					Package:  pkg, | ||||||
|  | 					Packages: pkgsMap[basePkgName].packages, | ||||||
|  | 					BuildArgs: BuildArgs{ | ||||||
|  | 						Opts:       input.BuildOpts(), | ||||||
|  | 						Info:       input.OSRelease(), | ||||||
|  | 						PkgFormat_: input.PkgFormat(), | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, nil, fmt.Errorf("failed build package from db: %w", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			buildDeps = append(buildDeps, res...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	repoDeps = removeDuplicates(repoDeps) | ||||||
|  | 	buildDeps = removeDuplicates(buildDeps) | ||||||
|  |  | ||||||
|  | 	return buildDeps, repoDeps, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *Builder) installBuildDeps( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input interface { | ||||||
|  | 		OsInfoProvider | ||||||
|  | 		BuildOptsProvider | ||||||
|  | 		PkgFormatProvider | ||||||
|  | 	}, | ||||||
|  | 	pkgs []string, | ||||||
|  | ) ([]*BuiltDep, []string, error) { | ||||||
|  | 	var builtDeps []*BuiltDep | ||||||
|  | 	var deps []string | ||||||
|  | 	var err error | ||||||
|  | 	if len(pkgs) > 0 { | ||||||
|  | 		deps, err = i.installerExecutor.RemoveAlreadyInstalled(ctx, pkgs) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		builtDeps, err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return builtDeps, deps, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *Builder) installOptDeps( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input interface { | ||||||
|  | 		OsInfoProvider | ||||||
|  | 		BuildOptsProvider | ||||||
|  | 		PkgFormatProvider | ||||||
|  | 	}, | ||||||
|  | 	pkgs []string, | ||||||
|  | ) ([]*BuiltDep, error) { | ||||||
|  | 	var builtDeps []*BuiltDep | ||||||
|  | 	optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(ctx, pkgs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if len(optDeps) > 0 { | ||||||
|  | 		optDeps, err := cliutils.ChooseOptDepends( | ||||||
|  | 			ctx, | ||||||
|  | 			optDeps, | ||||||
|  | 			"install", | ||||||
|  | 			input.BuildOpts().Interactive, | ||||||
|  | 		) // Пользователя просят выбрать опциональные зависимости | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if len(optDeps) == 0 { | ||||||
|  | 			return builtDeps, nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		builtDeps, err = i.InstallPkgs(ctx, input, optDeps) // Устанавливаем выбранные пакеты | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return builtDeps, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *Builder) InstallPkgs( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input interface { | ||||||
|  | 		OsInfoProvider | ||||||
|  | 		BuildOptsProvider | ||||||
|  | 		PkgFormatProvider | ||||||
|  | 	}, | ||||||
|  | 	pkgs []string, | ||||||
|  | ) ([]*BuiltDep, error) { | ||||||
|  | 	builtDeps, repoDeps, err := i.BuildALRDeps(ctx, input, pkgs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(builtDeps) > 0 { | ||||||
|  | 		err = i.installerExecutor.InstallLocal(ctx, GetBuiltPaths(builtDeps), &manager.Opts{ | ||||||
|  | 			NoConfirm: !input.BuildOpts().Interactive, | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		// Отслеживание установки локальных пакетов | ||||||
|  | 		for _, dep := range builtDeps { | ||||||
|  | 			if stats.ShouldTrackPackage(dep.Name) { | ||||||
|  | 				stats.TrackInstallation(ctx, dep.Name, "install") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(repoDeps) > 0 { | ||||||
|  | 		err = i.installerExecutor.Install(ctx, repoDeps, &manager.Opts{ | ||||||
|  | 			NoConfirm: !input.BuildOpts().Interactive, | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		// Отслеживание установки пакетов из репозитория | ||||||
|  | 		for _, pkg := range repoDeps { | ||||||
|  | 			if stats.ShouldTrackPackage(pkg) { | ||||||
|  | 				stats.TrackInstallation(ctx, pkg, "install") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return builtDeps, nil | ||||||
|  | } | ||||||
							
								
								
									
										286
									
								
								internal/build/build_internal_test.need-to-update
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								internal/build/build_internal_test.need-to-update
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,286 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"mvdan.cc/sh/v3/syntax" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type TestPackageFinder struct { | ||||||
|  | 	FindPkgsFunc func(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (pf *TestPackageFinder) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) { | ||||||
|  | 	if pf.FindPkgsFunc != nil { | ||||||
|  | 		return pf.FindPkgsFunc(ctx, pkgs) | ||||||
|  | 	} | ||||||
|  | 	return map[string][]db.Package{}, []string{}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TestManager struct { | ||||||
|  | 	NameFunc          func() string | ||||||
|  | 	FormatFunc        func() string | ||||||
|  | 	ExistsFunc        func() bool | ||||||
|  | 	SetRootCmdFunc    func(cmd string) | ||||||
|  | 	SyncFunc          func(opts *manager.Opts) error | ||||||
|  | 	InstallFunc       func(opts *manager.Opts, pkgs ...string) error | ||||||
|  | 	RemoveFunc        func(opts *manager.Opts, pkgs ...string) error | ||||||
|  | 	UpgradeFunc       func(opts *manager.Opts, pkgs ...string) error | ||||||
|  | 	InstallLocalFunc  func(opts *manager.Opts, files ...string) error | ||||||
|  | 	UpgradeAllFunc    func(opts *manager.Opts) error | ||||||
|  | 	ListInstalledFunc func(opts *manager.Opts) (map[string]string, error) | ||||||
|  | 	IsInstalledFunc   func(pkg string) (bool, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) Name() string { | ||||||
|  | 	if m.NameFunc != nil { | ||||||
|  | 		return m.NameFunc() | ||||||
|  | 	} | ||||||
|  | 	return "TestManager" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) Format() string { | ||||||
|  | 	if m.FormatFunc != nil { | ||||||
|  | 		return m.FormatFunc() | ||||||
|  | 	} | ||||||
|  | 	return "testpkg" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) Exists() bool { | ||||||
|  | 	if m.ExistsFunc != nil { | ||||||
|  | 		return m.ExistsFunc() | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) SetRootCmd(cmd string) { | ||||||
|  | 	if m.SetRootCmdFunc != nil { | ||||||
|  | 		m.SetRootCmdFunc(cmd) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) Sync(opts *manager.Opts) error { | ||||||
|  | 	if m.SyncFunc != nil { | ||||||
|  | 		return m.SyncFunc(opts) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) Install(opts *manager.Opts, pkgs ...string) error { | ||||||
|  | 	if m.InstallFunc != nil { | ||||||
|  | 		return m.InstallFunc(opts, pkgs...) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) Remove(opts *manager.Opts, pkgs ...string) error { | ||||||
|  | 	if m.RemoveFunc != nil { | ||||||
|  | 		return m.RemoveFunc(opts, pkgs...) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) Upgrade(opts *manager.Opts, pkgs ...string) error { | ||||||
|  | 	if m.UpgradeFunc != nil { | ||||||
|  | 		return m.UpgradeFunc(opts, pkgs...) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) InstallLocal(opts *manager.Opts, files ...string) error { | ||||||
|  | 	if m.InstallLocalFunc != nil { | ||||||
|  | 		return m.InstallLocalFunc(opts, files...) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) UpgradeAll(opts *manager.Opts) error { | ||||||
|  | 	if m.UpgradeAllFunc != nil { | ||||||
|  | 		return m.UpgradeAllFunc(opts) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) ListInstalled(opts *manager.Opts) (map[string]string, error) { | ||||||
|  | 	if m.ListInstalledFunc != nil { | ||||||
|  | 		return m.ListInstalledFunc(opts) | ||||||
|  | 	} | ||||||
|  | 	return map[string]string{}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *TestManager) IsInstalled(pkg string) (bool, error) { | ||||||
|  | 	if m.IsInstalledFunc != nil { | ||||||
|  | 		return m.IsInstalledFunc(pkg) | ||||||
|  | 	} | ||||||
|  | 	return true, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TestConfig struct{} | ||||||
|  |  | ||||||
|  | func (c *TestConfig) PagerStyle() string { | ||||||
|  | 	return "native" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *TestConfig) GetPaths() *config.Paths { | ||||||
|  | 	return &config.Paths{ | ||||||
|  | 		CacheDir: "/tmp", | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestExecuteFirstPassIsSecure(t *testing.T) { | ||||||
|  | 	cfg := &TestConfig{} | ||||||
|  | 	pf := &TestPackageFinder{} | ||||||
|  | 	info := &distro.OSRelease{} | ||||||
|  | 	m := &TestManager{} | ||||||
|  |  | ||||||
|  | 	opts := types.BuildOpts{ | ||||||
|  | 		Manager:     m, | ||||||
|  | 		Interactive: false, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx := context.Background() | ||||||
|  |  | ||||||
|  | 	b := NewBuilder( | ||||||
|  | 		ctx, | ||||||
|  | 		opts, | ||||||
|  | 		pf, | ||||||
|  | 		info, | ||||||
|  | 		cfg, | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	tmpFile, err := os.CreateTemp("", "testfile-") | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	tmpFilePath := tmpFile.Name() | ||||||
|  | 	defer os.Remove(tmpFilePath) | ||||||
|  |  | ||||||
|  | 	_, err = os.Stat(tmpFilePath) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	testScript := fmt.Sprintf(`name='test' | ||||||
|  | version=1.0.0 | ||||||
|  | release=1 | ||||||
|  | rm -f %s`, tmpFilePath) | ||||||
|  |  | ||||||
|  | 	fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "alr.sh") | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	_, _, err = b.executeFirstPass(fl) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	_, err = os.Stat(tmpFilePath) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestExecuteFirstPassIsCorrect(t *testing.T) { | ||||||
|  | 	type testCase struct { | ||||||
|  | 		Name     string | ||||||
|  | 		Script   string | ||||||
|  | 		Opts     types.BuildOpts | ||||||
|  | 		Expected func(t *testing.T, vars []*types.BuildVars) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tc := range []testCase{{ | ||||||
|  | 		Name: "single package", | ||||||
|  | 		Script: `name='test' | ||||||
|  | version='1.0.0' | ||||||
|  | release=1 | ||||||
|  | epoch=2 | ||||||
|  | desc="Test package" | ||||||
|  | homepage='https://example.com' | ||||||
|  | maintainer='Ivan Ivanov' | ||||||
|  | `, | ||||||
|  | 		Opts: types.BuildOpts{ | ||||||
|  | 			Manager:     &TestManager{}, | ||||||
|  | 			Interactive: false, | ||||||
|  | 		}, | ||||||
|  | 		Expected: func(t *testing.T, vars []*types.BuildVars) { | ||||||
|  | 			assert.Equal(t, 1, len(vars)) | ||||||
|  | 			assert.Equal(t, vars[0].Name, "test") | ||||||
|  | 			assert.Equal(t, vars[0].Version, "1.0.0") | ||||||
|  | 			assert.Equal(t, vars[0].Release, int(1)) | ||||||
|  | 			assert.Equal(t, vars[0].Epoch, uint(2)) | ||||||
|  | 			assert.Equal(t, vars[0].Description, "Test package") | ||||||
|  | 		}, | ||||||
|  | 	}, { | ||||||
|  | 		Name: "multiple packages", | ||||||
|  | 		Script: `name=( | ||||||
|  | 	foo | ||||||
|  | 	bar | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | version='0.0.1' | ||||||
|  | release=1 | ||||||
|  | epoch=2 | ||||||
|  | desc="Test package" | ||||||
|  |  | ||||||
|  | meta_foo() { | ||||||
|  | 	desc="Foo package" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | meta_bar() { | ||||||
|  |  | ||||||
|  | } | ||||||
|  | `, | ||||||
|  | 		Opts: types.BuildOpts{ | ||||||
|  | 			Packages:    []string{"foo"}, | ||||||
|  | 			Manager:     &TestManager{}, | ||||||
|  | 			Interactive: false, | ||||||
|  | 		}, | ||||||
|  | 		Expected: func(t *testing.T, vars []*types.BuildVars) { | ||||||
|  | 			assert.Equal(t, 1, len(vars)) | ||||||
|  | 			assert.Equal(t, vars[0].Name, "foo") | ||||||
|  | 			assert.Equal(t, vars[0].Description, "Foo package") | ||||||
|  | 		}, | ||||||
|  | 	}} { | ||||||
|  | 		t.Run(tc.Name, func(t *testing.T) { | ||||||
|  | 			cfg := &TestConfig{} | ||||||
|  | 			pf := &TestPackageFinder{} | ||||||
|  | 			info := &distro.OSRelease{} | ||||||
|  |  | ||||||
|  | 			ctx := context.Background() | ||||||
|  |  | ||||||
|  | 			b := NewBuilder( | ||||||
|  | 				ctx, | ||||||
|  | 				tc.Opts, | ||||||
|  | 				pf, | ||||||
|  | 				info, | ||||||
|  | 				cfg, | ||||||
|  | 			) | ||||||
|  |  | ||||||
|  | 			fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh") | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			_, allVars, err := b.scriptExecutor.ExecuteSecondPass(fl) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			tc.Expected(t, allVars) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										69
									
								
								internal/build/cache.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								internal/build/cache.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  |  | ||||||
|  | 	"github.com/goreleaser/nfpm/v2" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Cache struct { | ||||||
|  | 	cfg Config | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Cache) CheckForBuiltPackage( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input *BuildInput, | ||||||
|  | 	vars *alrsh.Package, | ||||||
|  | ) (string, bool, error) { | ||||||
|  | 	filename, err := pkgFileName(input, vars) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	pkgPath := filepath.Join(getBaseDir(c.cfg, vars.Name), filename) | ||||||
|  |  | ||||||
|  | 	_, err = os.Stat(pkgPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", false, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return pkgPath, true, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func pkgFileName( | ||||||
|  | 	input interface { | ||||||
|  | 		OsInfoProvider | ||||||
|  | 		PkgFormatProvider | ||||||
|  | 		RepositoryProvider | ||||||
|  | 	}, | ||||||
|  | 	vars *alrsh.Package, | ||||||
|  | ) (string, error) { | ||||||
|  | 	pkgInfo := getBasePkgInfo(vars, input) | ||||||
|  |  | ||||||
|  | 	packager, err := nfpm.Get(input.PkgFormat()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return packager.ConventionalFileName(pkgInfo), nil | ||||||
|  | } | ||||||
							
								
								
									
										74
									
								
								internal/build/checker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								internal/build/checker.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"log/slog" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Checker struct { | ||||||
|  | 	mgr manager.Manager | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Checker) PerformChecks( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input *BuildInput, | ||||||
|  | 	vars *alrsh.Package, | ||||||
|  | ) (bool, error) { | ||||||
|  | 	if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры | ||||||
|  | 		cont, err := cliutils.YesNoPrompt( | ||||||
|  | 			ctx, | ||||||
|  | 			gotext.Get("Your system's CPU architecture doesn't match this package. Do you want to build anyway?"), | ||||||
|  | 			input.opts.Interactive, | ||||||
|  | 			true, | ||||||
|  | 		) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return false, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !cont { | ||||||
|  | 			return false, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	installed, err := c.mgr.ListInstalled(nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	filename, err := pkgFileName(input, vars) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if instVer, ok := installed[filename]; ok { // Если пакет уже установлен, выводим предупреждение | ||||||
|  | 		slog.Warn(gotext.Get("This package is already installed"), | ||||||
|  | 			"name", vars.Name, | ||||||
|  | 			"version", instVer, | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return true, nil | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								internal/build/dirs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								internal/build/dirs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"path/filepath" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type BaseDirProvider interface { | ||||||
|  | 	BaseDir() string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type SrcDirProvider interface { | ||||||
|  | 	SrcDir() string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type PkgDirProvider interface { | ||||||
|  | 	PkgDir() string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ScriptDirProvider interface { | ||||||
|  | 	ScriptDir() string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getDirs( | ||||||
|  | 	cfg Config, | ||||||
|  | 	scriptPath string, | ||||||
|  | 	basePkg string, | ||||||
|  | ) (types.Directories, error) { | ||||||
|  | 	pkgsDir := cfg.GetPaths().PkgsDir | ||||||
|  |  | ||||||
|  | 	scriptPath, err := filepath.Abs(scriptPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return types.Directories{}, err | ||||||
|  | 	} | ||||||
|  | 	baseDir := filepath.Join(pkgsDir, basePkg) | ||||||
|  | 	return types.Directories{ | ||||||
|  | 		BaseDir:   getBaseDir(cfg, basePkg), | ||||||
|  | 		SrcDir:    getSrcDir(cfg, basePkg), | ||||||
|  | 		PkgDir:    filepath.Join(baseDir, "pkg"), | ||||||
|  | 		ScriptDir: getScriptDir(scriptPath), | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getBaseDir(cfg Config, basePkg string) string { | ||||||
|  | 	return filepath.Join(cfg.GetPaths().PkgsDir, basePkg) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getSrcDir(cfg Config, basePkg string) string { | ||||||
|  | 	return filepath.Join(getBaseDir(cfg, basePkg), "src") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getScriptDir(scriptPath string) string { | ||||||
|  | 	return filepath.Dir(scriptPath) | ||||||
|  | } | ||||||
							
								
								
									
										96
									
								
								internal/build/find_deps/alt_linux.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								internal/build/find_deps/alt_linux.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package finddeps | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"path" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/goreleaser/nfpm/v2" | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, envs []string, updateFunc func(string)) error { | ||||||
|  | 	if _, err := exec.LookPath(command); err != nil { | ||||||
|  | 		slog.Info(gotext.Get("Command not found on the system"), "command", command) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var paths []string | ||||||
|  | 	for _, content := range pkgInfo.Contents { | ||||||
|  | 		if content.Type != "dir" { | ||||||
|  | 			paths = append(paths, | ||||||
|  | 				path.Join(dirs.PkgDir, content.Destination), | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(paths) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd := exec.CommandContext(ctx, command) | ||||||
|  | 	cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n") + "\n") | ||||||
|  | 	cmd.Env = append(cmd.Env, | ||||||
|  | 		"RPM_BUILD_ROOT="+dirs.PkgDir, | ||||||
|  | 		"RPM_FINDPROV_METHOD=", | ||||||
|  | 		"RPM_FINDREQ_METHOD=", | ||||||
|  | 		"RPM_DATADIR=", | ||||||
|  | 		"RPM_SUBPACKAGE_NAME=", | ||||||
|  | 	) | ||||||
|  | 	cmd.Env = append(cmd.Env, envs...) | ||||||
|  | 	var out bytes.Buffer | ||||||
|  | 	var stderr bytes.Buffer | ||||||
|  | 	cmd.Stdout = &out | ||||||
|  | 	cmd.Stderr = &stderr | ||||||
|  | 	if err := cmd.Run(); err != nil { | ||||||
|  | 		slog.Error(stderr.String()) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	slog.Debug(stderr.String()) | ||||||
|  |  | ||||||
|  | 	dependencies := strings.Split(strings.TrimSpace(out.String()), "\n") | ||||||
|  | 	for _, dep := range dependencies { | ||||||
|  | 		if dep != "" { | ||||||
|  | 			updateFunc(dep) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ALTLinuxFindProvReq struct{} | ||||||
|  |  | ||||||
|  | func (o *ALTLinuxFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||||
|  | 	return rpmFindDependenciesALTLinux(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-provides", []string{"RPM_FINDPROV_SKIPLIST=" + strings.Join(skiplist, "\n")}, func(dep string) { | ||||||
|  | 		slog.Info(gotext.Get("Provided dependency found"), "dep", dep) | ||||||
|  | 		pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *ALTLinuxFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||||
|  | 	return rpmFindDependenciesALTLinux(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", []string{"RPM_FINDREQ_SKIPLIST=" + strings.Join(skiplist, "\n")}, func(dep string) { | ||||||
|  | 		slog.Info(gotext.Get("Required dependency found"), "dep", dep) | ||||||
|  | 		pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								internal/build/find_deps/empty.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								internal/build/find_deps/empty.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package finddeps | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"log/slog" | ||||||
|  |  | ||||||
|  | 	"github.com/goreleaser/nfpm/v2" | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type EmptyFindProvReq struct{} | ||||||
|  |  | ||||||
|  | func (o *EmptyFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||||
|  | 	slog.Info(gotext.Get("AutoProv is not implemented for this package format, so it's skipped")) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *EmptyFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||||
|  | 	slog.Info(gotext.Get("AutoReq is not implemented for this package format, so it's skipped")) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										118
									
								
								internal/build/find_deps/fedora.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								internal/build/find_deps/fedora.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package finddeps | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"path" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/goreleaser/nfpm/v2" | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type FedoraFindProvReq struct{} | ||||||
|  |  | ||||||
|  | func rpmFindDependenciesFedora(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, args []string, updateFunc func(string)) error { | ||||||
|  | 	if _, err := exec.LookPath(command); err != nil { | ||||||
|  | 		slog.Info(gotext.Get("Command not found on the system"), "command", command) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var paths []string | ||||||
|  | 	for _, content := range pkgInfo.Contents { | ||||||
|  | 		if content.Type != "dir" { | ||||||
|  | 			paths = append(paths, | ||||||
|  | 				path.Join(dirs.PkgDir, content.Destination), | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(paths) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd := exec.CommandContext(ctx, command, args...) | ||||||
|  | 	cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n") + "\n") | ||||||
|  | 	cmd.Env = append(cmd.Env, | ||||||
|  | 		"RPM_BUILD_ROOT="+dirs.PkgDir, | ||||||
|  | 	) | ||||||
|  | 	var out bytes.Buffer | ||||||
|  | 	var stderr bytes.Buffer | ||||||
|  | 	cmd.Stdout = &out | ||||||
|  | 	cmd.Stderr = &stderr | ||||||
|  | 	if err := cmd.Run(); err != nil { | ||||||
|  | 		slog.Error(stderr.String()) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	slog.Debug(stderr.String()) | ||||||
|  |  | ||||||
|  | 	dependencies := strings.Split(strings.TrimSpace(out.String()), "\n") | ||||||
|  | 	for _, dep := range dependencies { | ||||||
|  | 		if dep != "" { | ||||||
|  | 			updateFunc(dep) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *FedoraFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||||
|  | 	return rpmFindDependenciesFedora( | ||||||
|  | 		ctx, | ||||||
|  | 		pkgInfo, | ||||||
|  | 		dirs, | ||||||
|  | 		"/usr/lib/rpm/rpmdeps", | ||||||
|  | 		[]string{ | ||||||
|  | 			"--define=_use_internal_dependency_generator 1", | ||||||
|  | 			"--provides", | ||||||
|  | 			fmt.Sprintf( | ||||||
|  | 				"--define=__provides_exclude_from %s\"", | ||||||
|  | 				strings.Join(skiplist, "|"), | ||||||
|  | 			), | ||||||
|  | 		}, | ||||||
|  | 		func(dep string) { | ||||||
|  | 			slog.Info(gotext.Get("Provided dependency found"), "dep", dep) | ||||||
|  | 			pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep) | ||||||
|  | 		}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *FedoraFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||||
|  | 	return rpmFindDependenciesFedora( | ||||||
|  | 		ctx, | ||||||
|  | 		pkgInfo, | ||||||
|  | 		dirs, | ||||||
|  | 		"/usr/lib/rpm/rpmdeps", | ||||||
|  | 		[]string{ | ||||||
|  | 			"--define=_use_internal_dependency_generator 1", | ||||||
|  | 			"--requires", | ||||||
|  | 			fmt.Sprintf( | ||||||
|  | 				"--define=__requires_exclude_from %s", | ||||||
|  | 				strings.Join(skiplist, "|"), | ||||||
|  | 			), | ||||||
|  | 		}, | ||||||
|  | 		func(dep string) { | ||||||
|  | 			slog.Info(gotext.Get("Required dependency found"), "dep", dep) | ||||||
|  | 			pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep) | ||||||
|  | 		}) | ||||||
|  | } | ||||||
							
								
								
									
										58
									
								
								internal/build/find_deps/find_deps.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								internal/build/find_deps/find_deps.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package finddeps | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	"github.com/goreleaser/nfpm/v2" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type ProvReqFinder interface { | ||||||
|  | 	FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error | ||||||
|  | 	FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ProvReqService struct { | ||||||
|  | 	finder ProvReqFinder | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func New(info *distro.OSRelease, pkgFormat string) *ProvReqService { | ||||||
|  | 	s := &ProvReqService{ | ||||||
|  | 		finder: &EmptyFindProvReq{}, | ||||||
|  | 	} | ||||||
|  | 	if pkgFormat == "rpm" { | ||||||
|  | 		switch info.ID { | ||||||
|  | 		case "altlinux": | ||||||
|  | 			s.finder = &ALTLinuxFindProvReq{} | ||||||
|  | 		case "fedora": | ||||||
|  | 			s.finder = &FedoraFindProvReq{} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ProvReqService) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||||
|  | 	return s.finder.FindProvides(ctx, pkgInfo, dirs, skiplist) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ProvReqService) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error { | ||||||
|  | 	return s.finder.FindRequires(ctx, pkgInfo, dirs, skiplist) | ||||||
|  | } | ||||||
							
								
								
									
										296
									
								
								internal/build/firejail.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								internal/build/firejail.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,296 @@ | |||||||
|  | // 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 | ||||||
|  | } | ||||||
							
								
								
									
										322
									
								
								internal/build/firejail_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										322
									
								
								internal/build/firejail_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,322 @@ | |||||||
|  | // 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=") | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								internal/build/installer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								internal/build/installer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func NewInstaller(mgr manager.Manager) *Installer { | ||||||
|  | 	return &Installer{ | ||||||
|  | 		mgr: mgr, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Installer struct{ mgr manager.Manager } | ||||||
|  |  | ||||||
|  | func (i *Installer) InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error { | ||||||
|  | 	return i.mgr.InstallLocal(opts, paths...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *Installer) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error { | ||||||
|  | 	return i.mgr.Install(opts, pkgs...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *Installer) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error { | ||||||
|  | 	return i.mgr.Remove(opts, pkgs...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *Installer) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error) { | ||||||
|  | 	filteredPackages := []string{} | ||||||
|  |  | ||||||
|  | 	for _, dep := range pkgs { | ||||||
|  | 		installed, err := i.mgr.IsInstalled(dep) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if installed { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		filteredPackages = append(filteredPackages, dep) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return filteredPackages, nil | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								internal/build/main_build.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								internal/build/main_build.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func NewMainBuilder( | ||||||
|  | 	cfg Config, | ||||||
|  | 	mgr manager.Manager, | ||||||
|  | 	repos PackageFinder, | ||||||
|  | 	scriptExecutor ScriptExecutor, | ||||||
|  | 	installerExecutor InstallerExecutor, | ||||||
|  | ) (*Builder, error) { | ||||||
|  | 	builder := &Builder{ | ||||||
|  | 		scriptExecutor: scriptExecutor, | ||||||
|  | 		cacheExecutor: &Cache{ | ||||||
|  | 			cfg, | ||||||
|  | 		}, | ||||||
|  | 		scriptResolver: &ScriptResolver{ | ||||||
|  | 			cfg, | ||||||
|  | 		}, | ||||||
|  | 		scriptViewerExecutor: &ScriptViewer{ | ||||||
|  | 			config: cfg, | ||||||
|  | 		}, | ||||||
|  | 		checkerExecutor: &Checker{ | ||||||
|  | 			mgr, | ||||||
|  | 		}, | ||||||
|  | 		installerExecutor: installerExecutor, | ||||||
|  | 		sourceExecutor: &SourceDownloader{ | ||||||
|  | 			cfg, | ||||||
|  | 		}, | ||||||
|  | 		repos: repos, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return builder, nil | ||||||
|  | } | ||||||
							
								
								
									
										142
									
								
								internal/build/plugins.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								internal/build/plugins.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | |||||||
|  | // 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 | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								internal/build/plugins_executors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								internal/build/plugins_executors.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | // 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) | ||||||
|  | } | ||||||
							
								
								
									
										369
									
								
								internal/build/plugins_executors_gen.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								internal/build/plugins_executors_gen.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,369 @@ | |||||||
|  | // 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 | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								internal/build/repos_executor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								internal/build/repos_executor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type reposExecutor struct{ r *repos.Repos } | ||||||
|  |  | ||||||
|  | func NewRepos(r *repos.Repos) ReposExecutor { | ||||||
|  | 	return &reposExecutor{r} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *reposExecutor) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error) { | ||||||
|  | 	if err := r.r.PullOneAndUpdateFromConfig(ctx, repo); err != nil { | ||||||
|  | 		return *repo, err | ||||||
|  | 	} | ||||||
|  | 	return *repo, nil | ||||||
|  | } | ||||||
							
								
								
									
										379
									
								
								internal/build/script_executor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										379
									
								
								internal/build/script_executor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,379 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"slices" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/google/shlex" | ||||||
|  | 	"github.com/goreleaser/nfpm/v2" | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  | 	"mvdan.cc/sh/v3/expand" | ||||||
|  | 	"mvdan.cc/sh/v3/interp" | ||||||
|  | 	"mvdan.cc/sh/v3/syntax" | ||||||
|  |  | ||||||
|  | 	finddeps "gitea.plemya-x.ru/Plemya-x/ALR/internal/build/find_deps" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type LocalScriptExecutor struct { | ||||||
|  | 	cfg Config | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewLocalScriptExecutor(cfg Config) *LocalScriptExecutor { | ||||||
|  | 	return &LocalScriptExecutor{ | ||||||
|  | 		cfg, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) { | ||||||
|  | 	return alrsh.ReadFromLocal(scriptPath) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) { | ||||||
|  | 	return sf.ParseBuildVars(ctx, input.info, input.packages) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *LocalScriptExecutor) PrepareDirs( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input *BuildInput, | ||||||
|  | 	basePkg string, | ||||||
|  | ) error { | ||||||
|  | 	dirs, err := getDirs( | ||||||
|  | 		e.cfg, | ||||||
|  | 		input.script, | ||||||
|  | 		basePkg, | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = prepareDirs(dirs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *LocalScriptExecutor) ExecuteSecondPass( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input *BuildInput, | ||||||
|  | 	sf *alrsh.ScriptFile, | ||||||
|  | 	varsOfPackages []*alrsh.Package, | ||||||
|  | 	repoDeps []string, | ||||||
|  | 	builtDeps []*BuiltDep, | ||||||
|  | 	basePkg string, | ||||||
|  | ) ([]*BuiltDep, error) { | ||||||
|  | 	dirs, err := getDirs(e.cfg, sf.Path(), basePkg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	env := createBuildEnvVars(input.info, dirs) | ||||||
|  |  | ||||||
|  | 	fakeroot := handlers.FakerootExecHandler(2 * time.Second) | ||||||
|  | 	runner, err := interp.New( | ||||||
|  | 		interp.Env(expand.ListEnviron(env...)),       // Устанавливаем окружение | ||||||
|  | 		interp.StdIO(os.Stdin, os.Stderr, os.Stderr), // Устанавливаем стандартный ввод-вывод | ||||||
|  | 		interp.ExecHandlers(func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc { | ||||||
|  | 			return helpers.Helpers.ExecHandler(fakeroot) | ||||||
|  | 		}), // Обрабатываем выполнение через fakeroot | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = runner.Run(ctx, sf.File()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dec := decoder.New(input.info, runner) | ||||||
|  |  | ||||||
|  | 	// var builtPaths []string | ||||||
|  |  | ||||||
|  | 	err = e.ExecuteFunctions(ctx, dirs, dec) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, vars := range varsOfPackages { | ||||||
|  | 		packageName := "" | ||||||
|  | 		if vars.BasePkgName != "" { | ||||||
|  | 			packageName = vars.Name | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		pkgFormat := input.pkgFormat | ||||||
|  |  | ||||||
|  | 		funcOut, err := e.ExecutePackageFunctions( | ||||||
|  | 			ctx, | ||||||
|  | 			dec, | ||||||
|  | 			dirs, | ||||||
|  | 			packageName, | ||||||
|  | 		) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		slog.Info(gotext.Get("Building package metadata"), "name", basePkg) | ||||||
|  |  | ||||||
|  | 		pkgInfo, err := buildPkgMetadata( | ||||||
|  | 			ctx, | ||||||
|  | 			input, | ||||||
|  | 			vars, | ||||||
|  | 			dirs, | ||||||
|  | 			append( | ||||||
|  | 				repoDeps, | ||||||
|  | 				GetBuiltName(builtDeps)..., | ||||||
|  | 			), | ||||||
|  | 			funcOut.Contents, | ||||||
|  | 		) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета | ||||||
|  | 		pkgPath := filepath.Join(dirs.BaseDir, pkgName)   // Определяем путь к пакету | ||||||
|  |  | ||||||
|  | 		slog.Info("Creating package file", "path", pkgPath, "name", pkgName) | ||||||
|  |  | ||||||
|  | 		pkgFile, err := os.Create(pkgPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			slog.Error("Failed to create package file", "path", pkgPath, "error", err) | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		defer pkgFile.Close() | ||||||
|  |  | ||||||
|  | 		slog.Info("Packaging with nfpm", "format", pkgFormat) | ||||||
|  | 		err = packager.Package(pkgInfo, pkgFile) | ||||||
|  | 		if err != nil { | ||||||
|  | 			slog.Error("Failed to create package", "path", pkgPath, "error", err) | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		slog.Info("Package created successfully", "path", pkgPath) | ||||||
|  |  | ||||||
|  | 		// Проверяем, что файл действительно существует | ||||||
|  | 		if _, err := os.Stat(pkgPath); err != nil { | ||||||
|  | 			slog.Error("Package file not found after creation", "path", pkgPath, "error", err) | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		slog.Info("Package file verified to exist", "path", pkgPath) | ||||||
|  |  | ||||||
|  | 		builtDeps = append(builtDeps, &BuiltDep{ | ||||||
|  | 			Name: vars.Name, | ||||||
|  | 			Path: pkgPath, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return builtDeps, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func buildPkgMetadata( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input interface { | ||||||
|  | 		OsInfoProvider | ||||||
|  | 		BuildOptsProvider | ||||||
|  | 		PkgFormatProvider | ||||||
|  | 		RepositoryProvider | ||||||
|  | 	}, | ||||||
|  | 	vars *alrsh.Package, | ||||||
|  | 	dirs types.Directories, | ||||||
|  | 	deps []string, | ||||||
|  | 	preferedContents *[]string, | ||||||
|  | ) (*nfpm.Info, error) { | ||||||
|  | 	pkgInfo := getBasePkgInfo(vars, input) | ||||||
|  | 	pkgInfo.Description = vars.Description.Resolved() | ||||||
|  | 	pkgInfo.Platform = "linux" | ||||||
|  | 	pkgInfo.Homepage = vars.Homepage.Resolved() | ||||||
|  | 	pkgInfo.License = strings.Join(vars.Licenses, ", ") | ||||||
|  | 	pkgInfo.Maintainer = vars.Maintainer.Resolved() | ||||||
|  | 	pkgInfo.Overridables = nfpm.Overridables{ | ||||||
|  | 		Conflicts: append(vars.Conflicts, vars.Name), | ||||||
|  | 		Replaces:  vars.Replaces, | ||||||
|  | 		Provides:  append(vars.Provides, vars.Name), | ||||||
|  | 		Depends:   deps, | ||||||
|  | 	} | ||||||
|  | 	pkgInfo.Section = vars.Group.Resolved() | ||||||
|  |  | ||||||
|  | 	pkgFormat := input.PkgFormat() | ||||||
|  | 	info := input.OSRelease() | ||||||
|  |  | ||||||
|  | 	if pkgFormat == "apk" { | ||||||
|  | 		// Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы | ||||||
|  | 		pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool { | ||||||
|  | 			return s == pkgInfo.Name | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if pkgFormat == "rpm" { | ||||||
|  | 		pkgInfo.RPM.Group = vars.Group.Resolved() | ||||||
|  |  | ||||||
|  | 		if vars.Summary.Resolved() != "" { | ||||||
|  | 			pkgInfo.RPM.Summary = vars.Summary.Resolved() | ||||||
|  | 		} else { | ||||||
|  | 			lines := strings.SplitN(vars.Description.Resolved(), "\n", 2) | ||||||
|  | 			pkgInfo.RPM.Summary = lines[0] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Epoch != 0 { | ||||||
|  | 		pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	setScripts(vars, pkgInfo, dirs.ScriptDir) | ||||||
|  |  | ||||||
|  | 	if slices.Contains(vars.Architectures, "all") { | ||||||
|  | 		pkgInfo.Arch = "all" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	contents, err := buildContents(vars, dirs, preferedContents) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	normalizeContents(contents) | ||||||
|  |  | ||||||
|  | 	if vars.FireJailed.Resolved() { | ||||||
|  | 		contents, err = applyFirejailIntegration(vars, dirs, contents) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	pkgInfo.Overridables.Contents = contents | ||||||
|  |  | ||||||
|  | 	if len(vars.AutoProv.Resolved()) == 1 && decoder.IsTruthy(vars.AutoProv.Resolved()[0]) { | ||||||
|  | 		f := finddeps.New(info, pkgFormat) | ||||||
|  | 		err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList.Resolved()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(vars.AutoReq.Resolved()) == 1 && decoder.IsTruthy(vars.AutoReq.Resolved()[0]) { | ||||||
|  | 		f := finddeps.New(info, pkgFormat) | ||||||
|  | 		err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList.Resolved()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return pkgInfo, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *LocalScriptExecutor) ExecuteFunctions(ctx context.Context, dirs types.Directories, dec *decoder.Decoder) error { | ||||||
|  | 	prepare, ok := dec.GetFunc("prepare") | ||||||
|  | 	if ok { | ||||||
|  | 		slog.Info(gotext.Get("Executing prepare()")) | ||||||
|  |  | ||||||
|  | 		err := prepare(ctx, interp.Dir(dirs.SrcDir)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	build, ok := dec.GetFunc("build") | ||||||
|  | 	if ok { | ||||||
|  | 		slog.Info(gotext.Get("Executing build()")) | ||||||
|  |  | ||||||
|  | 		err := build(ctx, interp.Dir(dirs.SrcDir)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *LocalScriptExecutor) ExecutePackageFunctions( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	dec *decoder.Decoder, | ||||||
|  | 	dirs types.Directories, | ||||||
|  | 	packageName string, | ||||||
|  | ) (*FunctionsOutput, error) { | ||||||
|  | 	output := &FunctionsOutput{} | ||||||
|  | 	var packageFuncName string | ||||||
|  | 	var filesFuncName string | ||||||
|  |  | ||||||
|  | 	if packageName == "" { | ||||||
|  | 		packageFuncName = "package" | ||||||
|  | 		filesFuncName = "files" | ||||||
|  | 	} else { | ||||||
|  | 		packageFuncName = fmt.Sprintf("package_%s", packageName) | ||||||
|  | 		filesFuncName = fmt.Sprintf("files_%s", packageName) | ||||||
|  | 	} | ||||||
|  | 	packageFn, ok := dec.GetFunc(packageFuncName) | ||||||
|  | 	if ok { | ||||||
|  | 		slog.Info(gotext.Get("Executing %s()", packageFuncName)) | ||||||
|  | 		err := packageFn(ctx, interp.Dir(dirs.SrcDir)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	files, ok := dec.GetFuncP(filesFuncName, func(ctx context.Context, s *interp.Runner) error { | ||||||
|  | 		// It should be done via interp.RunnerOption, | ||||||
|  | 		// but due to the issues below, it cannot be done. | ||||||
|  | 		// - https://github.com/mvdan/sh/issues/962 | ||||||
|  | 		// - https://github.com/mvdan/sh/issues/1125 | ||||||
|  | 		script, err := syntax.NewParser().Parse(strings.NewReader("cd $pkgdir && shopt -s globstar"), "") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		return s.Run(ctx, script) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	if ok { | ||||||
|  | 		slog.Info(gotext.Get("Executing %s()", filesFuncName)) | ||||||
|  |  | ||||||
|  | 		buf := &bytes.Buffer{} | ||||||
|  |  | ||||||
|  | 		err := files( | ||||||
|  | 			ctx, | ||||||
|  | 			interp.Dir(dirs.PkgDir), | ||||||
|  | 			interp.StdIO(os.Stdin, buf, os.Stderr), | ||||||
|  | 		) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		contents, err := shlex.Split(buf.String()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		output.Contents = &contents | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return output, nil | ||||||
|  | } | ||||||
							
								
								
									
										65
									
								
								internal/build/script_resolver.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								internal/build/script_resolver.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type ScriptResolver struct { | ||||||
|  | 	cfg Config | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ScriptInfo struct { | ||||||
|  | 	Script     string | ||||||
|  | 	Repository string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ScriptResolver) ResolveScript( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	pkg *alrsh.Package, | ||||||
|  | ) *ScriptInfo { | ||||||
|  | 	var repository, script string | ||||||
|  |  | ||||||
|  | 	repodir := s.cfg.GetPaths().RepoDir | ||||||
|  | 	repository = pkg.Repository | ||||||
|  |  | ||||||
|  | 	// First, we check if there is a root alr.sh in the repository | ||||||
|  | 	rootScriptPath := filepath.Join(repodir, repository, "alr.sh") | ||||||
|  | 	if _, err := os.Stat(rootScriptPath); err == nil { | ||||||
|  | 		// A repository with a single alr.sh at the root | ||||||
|  | 		script = rootScriptPath | ||||||
|  | 	} else { | ||||||
|  | 		// Multi-package repository - we are looking for alr.sh in the subfolder | ||||||
|  | 		var scriptPath string | ||||||
|  | 		if pkg.BasePkgName != "" { | ||||||
|  | 			scriptPath = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh") | ||||||
|  | 		} else { | ||||||
|  | 			scriptPath = filepath.Join(repodir, repository, pkg.Name, "alr.sh") | ||||||
|  | 		} | ||||||
|  | 		script = scriptPath | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &ScriptInfo{ | ||||||
|  | 		Repository: repository, | ||||||
|  | 		Script:     script, | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								internal/build/script_view.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								internal/build/script_view.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type ScriptViewerConfig interface { | ||||||
|  | 	PagerStyle() string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ScriptViewer struct { | ||||||
|  | 	config ScriptViewerConfig | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ScriptViewer) ViewScript( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input *BuildInput, | ||||||
|  | 	a *alrsh.ScriptFile, | ||||||
|  | 	basePkg string, | ||||||
|  | ) error { | ||||||
|  | 	return cliutils.PromptViewScript( | ||||||
|  | 		ctx, | ||||||
|  | 		a.Path(), | ||||||
|  | 		basePkg, | ||||||
|  | 		s.config.PagerStyle(), | ||||||
|  | 		input.opts.Interactive, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										89
									
								
								internal/build/source_downloader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								internal/build/source_downloader.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/hex" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type SourceDownloader struct { | ||||||
|  | 	cfg Config | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewSourceDownloader(cfg Config) *SourceDownloader { | ||||||
|  | 	return &SourceDownloader{ | ||||||
|  | 		cfg, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *SourceDownloader) DownloadSources( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	input *BuildInput, | ||||||
|  | 	basePkg string, | ||||||
|  | 	si SourcesInput, | ||||||
|  | ) error { | ||||||
|  | 	for i, src := range si.Sources { | ||||||
|  |  | ||||||
|  | 		opts := dl.Options{ | ||||||
|  | 			Name:        fmt.Sprintf("[%d]", i), | ||||||
|  | 			URL:         src, | ||||||
|  | 			Destination: getSrcDir(s.cfg, basePkg), | ||||||
|  | 			Progress:    os.Stderr, | ||||||
|  | 			LocalDir:    getScriptDir(input.script), | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !strings.EqualFold(si.Checksums[i], "SKIP") { | ||||||
|  | 			// Если контрольная сумма содержит двоеточие, используйте часть до двоеточия | ||||||
|  | 			// как алгоритм, а часть после как фактическую контрольную сумму. | ||||||
|  | 			// В противном случае используйте sha256 по умолчанию с целой строкой как контрольной суммой. | ||||||
|  | 			algo, hashData, ok := strings.Cut(si.Checksums[i], ":") | ||||||
|  | 			if ok { | ||||||
|  | 				checksum, err := hex.DecodeString(hashData) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				opts.Hash = checksum | ||||||
|  | 				opts.HashAlgorithm = algo | ||||||
|  | 			} else { | ||||||
|  | 				checksum, err := hex.DecodeString(si.Checksums[i]) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				opts.Hash = checksum | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Используем временную директорию для загрузок | ||||||
|  | 		// dlcache.New добавит свой подкаталог "dl" внутри | ||||||
|  | 		opts.DlCache = dlcache.New(constants.TempDir) | ||||||
|  |  | ||||||
|  | 		err := dl.Download(ctx, opts) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										333
									
								
								internal/build/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								internal/build/utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,333 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"regexp" | ||||||
|  | 	"runtime" | ||||||
|  | 	"slices" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	// Импортируем пакеты для поддержки различных форматов пакетов (APK, DEB, RPM и ARCH). | ||||||
|  |  | ||||||
|  | 	_ "github.com/goreleaser/nfpm/v2/apk" | ||||||
|  | 	_ "github.com/goreleaser/nfpm/v2/arch" | ||||||
|  | 	_ "github.com/goreleaser/nfpm/v2/deb" | ||||||
|  | 	_ "github.com/goreleaser/nfpm/v2/rpm" | ||||||
|  |  | ||||||
|  | 	"github.com/goreleaser/nfpm/v2" | ||||||
|  | 	"github.com/goreleaser/nfpm/v2/files" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Функция prepareDirs подготавливает директории для сборки. | ||||||
|  | func prepareDirs(dirs types.Directories) error { | ||||||
|  | 	// Удаляем только директории источников и упаковки, не трогаем файлы пакетов в BaseDir | ||||||
|  | 	err := os.RemoveAll(dirs.SrcDir) | ||||||
|  | 	if err != nil { | ||||||
|  | 		slog.Debug("Failed to remove src directory", "path", dirs.SrcDir, "error", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = os.RemoveAll(dirs.PkgDir) | ||||||
|  | 	if err != nil { | ||||||
|  | 		slog.Debug("Failed to remove pkg directory", "path", dirs.PkgDir, "error", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Создаем базовую директорию для пакета с setgid битом | ||||||
|  | 	err = utils.EnsureTempDirWithRootOwner(dirs.BaseDir, 0o2775) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Создаем директории с правильным владельцем для /tmp/alr с setgid битом | ||||||
|  | 	err = utils.EnsureTempDirWithRootOwner(dirs.SrcDir, 0o2775) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Создаем директорию для пакетов с setgid битом | ||||||
|  | 	return utils.EnsureTempDirWithRootOwner(dirs.PkgDir, 0o2775) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция buildContents создает секцию содержимого пакета, которая содержит файлы, | ||||||
|  | // которые будут включены в конечный пакет. | ||||||
|  | func buildContents(vars *alrsh.Package, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) { | ||||||
|  | 	contents := []*files.Content{} | ||||||
|  |  | ||||||
|  | 	processPath := func(path, trimmed string, prefered bool) error { | ||||||
|  | 		fi, err := os.Lstat(path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if fi.IsDir() { | ||||||
|  | 			f, err := os.Open(path) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer f.Close() | ||||||
|  |  | ||||||
|  | 			if !prefered { | ||||||
|  | 				_, err = f.Readdirnames(1) | ||||||
|  | 				if err != io.EOF { | ||||||
|  | 					return nil | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			contents = append(contents, &files.Content{ | ||||||
|  | 				Source:      path, | ||||||
|  | 				Destination: trimmed, | ||||||
|  | 				Type:        "dir", | ||||||
|  | 				FileInfo: &files.ContentFileInfo{ | ||||||
|  | 					MTime: fi.ModTime(), | ||||||
|  | 				}, | ||||||
|  | 			}) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if fi.Mode()&os.ModeSymlink != 0 { | ||||||
|  | 			link, err := os.Readlink(path) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			link = strings.TrimPrefix(link, dirs.PkgDir) | ||||||
|  |  | ||||||
|  | 			contents = append(contents, &files.Content{ | ||||||
|  | 				Source:      link, | ||||||
|  | 				Destination: trimmed, | ||||||
|  | 				Type:        "symlink", | ||||||
|  | 				FileInfo: &files.ContentFileInfo{ | ||||||
|  | 					MTime: fi.ModTime(), | ||||||
|  | 					Mode:  fi.Mode(), | ||||||
|  | 				}, | ||||||
|  | 			}) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		fileContent := &files.Content{ | ||||||
|  | 			Source:      path, | ||||||
|  | 			Destination: trimmed, | ||||||
|  | 			FileInfo: &files.ContentFileInfo{ | ||||||
|  | 				MTime: fi.ModTime(), | ||||||
|  | 				Mode:  fi.Mode(), | ||||||
|  | 				Size:  fi.Size(), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if slices.Contains(vars.Backup.Resolved(), trimmed) { | ||||||
|  | 			fileContent.Type = "config|noreplace" | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		contents = append(contents, fileContent) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if preferedContents != nil { | ||||||
|  | 		for _, trimmed := range *preferedContents { | ||||||
|  | 			path := filepath.Join(dirs.PkgDir, trimmed) | ||||||
|  | 			if err := processPath(path, trimmed, true); err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error { | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			trimmed := strings.TrimPrefix(path, dirs.PkgDir) | ||||||
|  | 			return processPath(path, trimmed, false) | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return contents, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func normalizeContents(contents []*files.Content) { | ||||||
|  | 	for _, content := range contents { | ||||||
|  | 		content.Destination = filepath.Join("/", content.Destination) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+(?P<repo>.+)$`) | ||||||
|  |  | ||||||
|  | func getBasePkgInfo(vars *alrsh.Package, input interface { | ||||||
|  | 	RepositoryProvider | ||||||
|  | 	OsInfoProvider | ||||||
|  | }, | ||||||
|  | ) *nfpm.Info { | ||||||
|  | 	repo := input.Repository() | ||||||
|  | 	return &nfpm.Info{ | ||||||
|  | 		Name:    fmt.Sprintf("%s+%s", vars.Name, repo), | ||||||
|  | 		Arch:    cpu.Arch(), | ||||||
|  | 		Version: vars.Version, | ||||||
|  | 		Release: overrides.ReleasePlatformSpecific(vars.Release, input.OSRelease()), | ||||||
|  | 		Epoch:   strconv.FormatUint(uint64(vars.Epoch), 10), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция getPkgFormat возвращает формат пакета из менеджера пакетов, | ||||||
|  | // или ALR_PKG_FORMAT, если он установлен. | ||||||
|  | func GetPkgFormat(mgr manager.Manager) string { | ||||||
|  | 	pkgFormat := mgr.Format() | ||||||
|  | 	if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok { | ||||||
|  | 		pkgFormat = format | ||||||
|  | 	} | ||||||
|  | 	return pkgFormat | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция createBuildEnvVars создает переменные окружения, которые будут установлены | ||||||
|  | // в скрипте сборки при его выполнении. | ||||||
|  | func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string { | ||||||
|  | 	env := os.Environ() | ||||||
|  |  | ||||||
|  | 	env = append( | ||||||
|  | 		env, | ||||||
|  | 		"DISTRO_NAME="+info.Name, | ||||||
|  | 		"DISTRO_PRETTY_NAME="+info.PrettyName, | ||||||
|  | 		"DISTRO_ID="+info.ID, | ||||||
|  | 		"DISTRO_VERSION_ID="+info.VersionID, | ||||||
|  | 		"DISTRO_ID_LIKE="+strings.Join(info.Like, " "), | ||||||
|  | 		"ARCH="+cpu.Arch(), | ||||||
|  | 		"NCPU="+strconv.Itoa(runtime.NumCPU()), | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	if dirs.ScriptDir != "" { | ||||||
|  | 		env = append(env, "scriptdir="+dirs.ScriptDir) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if dirs.PkgDir != "" { | ||||||
|  | 		env = append(env, "pkgdir="+dirs.PkgDir) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if dirs.SrcDir != "" { | ||||||
|  | 		env = append(env, "srcdir="+dirs.SrcDir) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return env | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция setScripts добавляет скрипты-перехватчики к метаданным пакета. | ||||||
|  | func setScripts(vars *alrsh.Package, info *nfpm.Info, scriptDir string) { | ||||||
|  | 	if vars.Scripts.Resolved().PreInstall != "" { | ||||||
|  | 		info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PreInstall) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.Resolved().PostInstall != "" { | ||||||
|  | 		info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PostInstall) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.Resolved().PreRemove != "" { | ||||||
|  | 		info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PreRemove) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.Resolved().PostRemove != "" { | ||||||
|  | 		info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PostRemove) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.Resolved().PreUpgrade != "" { | ||||||
|  | 		info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade) | ||||||
|  | 		info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.Resolved().PostUpgrade != "" { | ||||||
|  | 		info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade) | ||||||
|  | 		info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.Resolved().PreTrans != "" { | ||||||
|  | 		info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PreTrans) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.Resolved().PostTrans != "" { | ||||||
|  | 		info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PostTrans) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | // Функция setVersion изменяет переменную версии в скрипте runner. | ||||||
|  | // Она используется для установки версии на вывод функции version(). | ||||||
|  | func setVersion(ctx context.Context, r *interp.Runner, to string) error { | ||||||
|  | 	fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return r.Run(ctx, fl) | ||||||
|  | } | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | // Функция packageNames возвращает имена всех предоставленных пакетов. | ||||||
|  | /* | ||||||
|  | func packageNames(pkgs []db.Package) []string { | ||||||
|  | 	names := make([]string, len(pkgs)) | ||||||
|  | 	for i, p := range pkgs { | ||||||
|  | 		names[i] = p.Name | ||||||
|  | 	} | ||||||
|  | 	return names | ||||||
|  | } | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | // Функция removeDuplicates убирает любые дубликаты из предоставленного среза. | ||||||
|  | func removeDuplicates[T comparable](slice []T) []T { | ||||||
|  | 	seen := map[T]struct{}{} | ||||||
|  | 	result := []T{} | ||||||
|  |  | ||||||
|  | 	for _, item := range slice { | ||||||
|  | 		if _, ok := seen[item]; !ok { | ||||||
|  | 			seen[item] = struct{}{} | ||||||
|  | 			result = append(result, item) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func removeDuplicatesSources(sources, checksums []string) ([]string, []string) { | ||||||
|  | 	seen := map[string]string{} | ||||||
|  | 	keys := make([]string, 0) | ||||||
|  | 	for i, s := range sources { | ||||||
|  | 		if val, ok := seen[s]; !ok || strings.EqualFold(val, "SKIP") { | ||||||
|  | 			if !ok { | ||||||
|  | 				keys = append(keys, s) | ||||||
|  | 			} | ||||||
|  | 			seen[s] = checksums[i] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	newSources := make([]string, len(keys)) | ||||||
|  | 	newChecksums := make([]string, len(keys)) | ||||||
|  | 	for i, k := range keys { | ||||||
|  | 		newSources[i] = k | ||||||
|  | 		newChecksums[i] = seen[k] | ||||||
|  | 	} | ||||||
|  | 	return newSources, newChecksums | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								internal/build/utils_internal_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								internal/build/utils_internal_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestRemoveDuplicatesSources(t *testing.T) { | ||||||
|  | 	type testCase struct { | ||||||
|  | 		Name         string | ||||||
|  | 		Sources      []string | ||||||
|  | 		Checksums    []string | ||||||
|  | 		NewSources   []string | ||||||
|  | 		NewChecksums []string | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tc := range []testCase{{ | ||||||
|  | 		Name:         "prefer non-skip values", | ||||||
|  | 		Sources:      []string{"a", "b", "c", "a"}, | ||||||
|  | 		Checksums:    []string{"skip", "skip", "skip", "1"}, | ||||||
|  | 		NewSources:   []string{"a", "b", "c"}, | ||||||
|  | 		NewChecksums: []string{"1", "skip", "skip"}, | ||||||
|  | 	}} { | ||||||
|  | 		t.Run(tc.Name, func(t *testing.T) { | ||||||
|  | 			s, c := removeDuplicatesSources(tc.Sources, tc.Checksums) | ||||||
|  | 			assert.Equal(t, s, tc.NewSources) | ||||||
|  | 			assert.Equal(t, c, tc.NewChecksums) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										158
									
								
								internal/build/utils_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								internal/build/utils_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,158 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type mockInput struct { | ||||||
|  | 	repo    string | ||||||
|  | 	osInfo  *distro.OSRelease | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *mockInput) Repository() string { | ||||||
|  | 	return m.repo | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *mockInput) OSRelease() *distro.OSRelease { | ||||||
|  | 	return m.osInfo | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGetBasePkgInfo(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name         string | ||||||
|  | 		packageName  string | ||||||
|  | 		repoName     string | ||||||
|  | 		expectedName string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:         "обычный репозиторий", | ||||||
|  | 			packageName:  "test-package", | ||||||
|  | 			repoName:     "default", | ||||||
|  | 			expectedName: "test-package+default", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "репозиторий с alr- префиксом", | ||||||
|  | 			packageName:  "test-package", | ||||||
|  | 			repoName:     "alr-default", | ||||||
|  | 			expectedName: "test-package+alr-default", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "репозиторий с двойным alr- префиксом", | ||||||
|  | 			packageName:  "test-package", | ||||||
|  | 			repoName:     "alr-alr-repo", | ||||||
|  | 			expectedName: "test-package+alr-alr-repo", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			pkg := &alrsh.Package{ | ||||||
|  | 				Name:    tt.packageName, | ||||||
|  | 				Version: "1.0.0", | ||||||
|  | 				Release: 1, | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			input := &mockInput{ | ||||||
|  | 				repo: tt.repoName, | ||||||
|  | 				osInfo: &distro.OSRelease{ | ||||||
|  | 					ID: "test", | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			info := getBasePkgInfo(pkg, input) | ||||||
|  |  | ||||||
|  | 			if info.Name != tt.expectedName { | ||||||
|  | 				t.Errorf("getBasePkgInfo() имя пакета = %v, ожидается %v", info.Name, tt.expectedName) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRegexpALRPackageName(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name         string | ||||||
|  | 		packageName  string | ||||||
|  | 		expectedPkg  string | ||||||
|  | 		expectedRepo string | ||||||
|  | 		shouldMatch  bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:         "новый формат - обычный репозиторий", | ||||||
|  | 			packageName:  "test-package+default", | ||||||
|  | 			expectedPkg:  "test-package", | ||||||
|  | 			expectedRepo: "default", | ||||||
|  | 			shouldMatch:  true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "новый формат - alr-default репозиторий", | ||||||
|  | 			packageName:  "test-package+alr-default", | ||||||
|  | 			expectedPkg:  "test-package", | ||||||
|  | 			expectedRepo: "alr-default", | ||||||
|  | 			shouldMatch:  true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "новый формат - двойной alr- префикс", | ||||||
|  | 			packageName:  "test-package+alr-alr-repo", | ||||||
|  | 			expectedPkg:  "test-package", | ||||||
|  | 			expectedRepo: "alr-alr-repo", | ||||||
|  | 			shouldMatch:  true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:        "некорректный формат - без плюса", | ||||||
|  | 			packageName: "test-package", | ||||||
|  | 			shouldMatch: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:        "некорректный формат - пустое имя пакета", | ||||||
|  | 			packageName: "+repo", | ||||||
|  | 			shouldMatch: false, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			matches := RegexpALRPackageName.FindStringSubmatch(tt.packageName) | ||||||
|  |  | ||||||
|  | 			if tt.shouldMatch { | ||||||
|  | 				if matches == nil { | ||||||
|  | 					t.Errorf("RegexpALRPackageName должен найти совпадение для %q", tt.packageName) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				packageName := matches[RegexpALRPackageName.SubexpIndex("package")] | ||||||
|  | 				repoName := matches[RegexpALRPackageName.SubexpIndex("repo")] | ||||||
|  |  | ||||||
|  | 				if packageName != tt.expectedPkg { | ||||||
|  | 					t.Errorf("RegexpALRPackageName извлеченное имя пакета = %v, ожидается %v", packageName, tt.expectedPkg) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if repoName != tt.expectedRepo { | ||||||
|  | 					t.Errorf("RegexpALRPackageName извлеченное имя репозитория = %v, ожидается %v", repoName, tt.expectedRepo) | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				if matches != nil { | ||||||
|  | 					t.Errorf("RegexpALRPackageName не должен найти совпадение для %q", tt.packageName) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										183
									
								
								internal/cliutils/app_builder/builder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								internal/cliutils/app_builder/builder.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package appbuilder | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"log/slog" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type AppDeps struct { | ||||||
|  | 	Cfg     *config.ALRConfig | ||||||
|  | 	DB      *db.Database | ||||||
|  | 	Repos   *repos.Repos | ||||||
|  | 	Info    *distro.OSRelease | ||||||
|  | 	Manager manager.Manager | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *AppDeps) Defer() { | ||||||
|  | 	if d.DB != nil { | ||||||
|  | 		if err := d.DB.Close(); err != nil { | ||||||
|  | 			slog.Warn("failed to close db", "err", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AppBuilder struct { | ||||||
|  | 	deps AppDeps | ||||||
|  | 	err  error | ||||||
|  | 	ctx  context.Context | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func New(ctx context.Context) *AppBuilder { | ||||||
|  | 	return &AppBuilder{ctx: ctx} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *AppBuilder) UseConfig(cfg *config.ALRConfig) *AppBuilder { | ||||||
|  | 	if b.err != nil { | ||||||
|  | 		return b | ||||||
|  | 	} | ||||||
|  | 	b.deps.Cfg = cfg | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *AppBuilder) WithConfig() *AppBuilder { | ||||||
|  | 	if b.err != nil { | ||||||
|  | 		return b | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cfg := config.New() | ||||||
|  | 	if err := cfg.Load(); err != nil { | ||||||
|  | 		b.err = cliutils.FormatCliExit(gotext.Get("Error loading config"), err) | ||||||
|  | 		return b | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b.deps.Cfg = cfg | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *AppBuilder) WithDB() *AppBuilder { | ||||||
|  | 	if b.err != nil { | ||||||
|  | 		return b | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cfg := b.deps.Cfg | ||||||
|  | 	if cfg == nil { | ||||||
|  | 		b.err = errors.New("config is required before initializing DB") | ||||||
|  | 		return b | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	db := db.New(cfg) | ||||||
|  | 	if err := db.Init(b.ctx); err != nil { | ||||||
|  | 		b.err = cliutils.FormatCliExit(gotext.Get("Error initialization database"), err) | ||||||
|  | 		return b | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b.deps.DB = db | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *AppBuilder) WithRepos() *AppBuilder { | ||||||
|  | 	b.withRepos(true, false) | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *AppBuilder) WithReposForcePull() *AppBuilder { | ||||||
|  | 	b.withRepos(true, true) | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *AppBuilder) WithReposNoPull() *AppBuilder { | ||||||
|  | 	b.withRepos(false, false) | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder { | ||||||
|  | 	if b.err != nil { | ||||||
|  | 		return b | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cfg := b.deps.Cfg | ||||||
|  | 	db := b.deps.DB | ||||||
|  | 	info := b.deps.Info | ||||||
|  |  | ||||||
|  | 	if info == nil { | ||||||
|  | 		b.WithDistroInfo() | ||||||
|  | 		info = b.deps.Info | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if cfg == nil || db == nil || info == nil { | ||||||
|  | 		b.err = errors.New("config, db and info are required before initializing repos") | ||||||
|  | 		return b | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rs := repos.New(cfg, db) | ||||||
|  |  | ||||||
|  | 	if enablePull && (forcePull || cfg.AutoPull()) { | ||||||
|  | 		if err := rs.Pull(b.ctx, cfg.Repos()); err != nil { | ||||||
|  | 			b.err = cliutils.FormatCliExit(gotext.Get("Error pulling repositories"), err) | ||||||
|  | 			return b | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b.deps.Repos = rs | ||||||
|  |  | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *AppBuilder) WithDistroInfo() *AppBuilder { | ||||||
|  | 	if b.err != nil { | ||||||
|  | 		return b | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b.deps.Info, b.err = distro.ParseOSRelease(b.ctx) | ||||||
|  | 	if b.err != nil { | ||||||
|  | 		b.err = cliutils.FormatCliExit(gotext.Get("Error parsing os release"), b.err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *AppBuilder) WithManager() *AppBuilder { | ||||||
|  | 	if b.err != nil { | ||||||
|  | 		return b | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b.deps.Manager = manager.Detect() | ||||||
|  | 	if b.deps.Manager == nil { | ||||||
|  | 		b.err = cliutils.FormatCliExit(gotext.Get("Unable to detect a supported package manager on the system"), nil) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *AppBuilder) Build() (*AppDeps, error) { | ||||||
|  | 	if b.err != nil { | ||||||
|  | 		return nil, b.err | ||||||
|  | 	} | ||||||
|  | 	return &b.deps, nil | ||||||
|  | } | ||||||
| @@ -1,34 +1,35 @@ | |||||||
| /* | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  * ALR - Any Linux Repository | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  * Copyright (C) 2024 Евгений Храмов | // | ||||||
|  * | // ALR - Any Linux Repository | ||||||
|  * This program is free software: you can redistribute it and/or modify | // Copyright (C) 2025 The ALR Authors | ||||||
|  * it under the terms of the GNU General Public License as published by | // | ||||||
|  * the Free Software Foundation, either version 3 of the License, or | // This program is free software: you can redistribute it and/or modify | ||||||
|  * (at your option) any later version. | // it under the terms of the GNU General Public License as published by | ||||||
|  * | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  * This program is distributed in the hope that it will be useful, | // (at your option) any later version. | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | // | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | // This program is distributed in the hope that it will be useful, | ||||||
|  * GNU General Public License for more details. | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  * | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  * You should have received a copy of the GNU General Public License | // GNU General Public License for more details. | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | // | ||||||
|  */ | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package cliutils | package cliutils | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"plemya-x.ru/alr/internal/config" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"plemya-x.ru/alr/internal/db" |  | ||||||
| 	"plemya-x.ru/alr/internal/pager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager" | ||||||
| 	"plemya-x.ru/alr/internal/translations" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 	"plemya-x.ru/alr/pkg/loggerctx" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // YesNoPrompt asks the user a yes or no question, using def as the default answer | // YesNoPrompt asks the user a yes or no question, using def as the default answer | ||||||
| @@ -37,7 +38,7 @@ func YesNoPrompt(ctx context.Context, msg string, interactive, def bool) (bool, | |||||||
| 		var answer bool | 		var answer bool | ||||||
| 		err := survey.AskOne( | 		err := survey.AskOne( | ||||||
| 			&survey.Confirm{ | 			&survey.Confirm{ | ||||||
| 				Message: translations.Translator(ctx).TranslateTo(msg, config.Language(ctx)), | 				Message: msg, | ||||||
| 				Default: def, | 				Default: def, | ||||||
| 			}, | 			}, | ||||||
| 			&answer, | 			&answer, | ||||||
| @@ -52,14 +53,11 @@ func YesNoPrompt(ctx context.Context, msg string, interactive, def bool) (bool, | |||||||
| // shows it if they answer yes, then asks if they'd still like to | // shows it if they answer yes, then asks if they'd still like to | ||||||
| // continue, and exits if they answer no. | // continue, and exits if they answer no. | ||||||
| func PromptViewScript(ctx context.Context, script, name, style string, interactive bool) error { | func PromptViewScript(ctx context.Context, script, name, style string, interactive bool) error { | ||||||
| 	log := loggerctx.From(ctx) |  | ||||||
|  |  | ||||||
| 	if !interactive { | 	if !interactive { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	scriptPrompt := translations.Translator(ctx).TranslateTo("Would you like to view the build script for", config.Language(ctx)) + " " + name | 	view, err := YesNoPrompt(ctx, gotext.Get("Would you like to view the build script for %s", name), interactive, false) | ||||||
| 	view, err := YesNoPrompt(ctx, scriptPrompt, interactive, false) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -70,13 +68,14 @@ func PromptViewScript(ctx context.Context, script, name, style string, interacti | |||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		cont, err := YesNoPrompt(ctx, "Would you still like to continue?", interactive, false) | 		cont, err := YesNoPrompt(ctx, gotext.Get("Would you still like to continue?"), interactive, false) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if !cont { | 		if !cont { | ||||||
| 			log.Fatal(translations.Translator(ctx).TranslateTo("User chose not to continue after reading script", config.Language(ctx))).Send() | 			slog.Error(gotext.Get("User chose not to continue after reading script")) | ||||||
|  | 			os.Exit(1) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -103,25 +102,65 @@ func ShowScript(path, name, style string) error { | |||||||
|  |  | ||||||
| // FlattenPkgs attempts to flatten the a map of slices of packages into a single slice | // FlattenPkgs attempts to flatten the a map of slices of packages into a single slice | ||||||
| // of packages by prompting the user if multiple packages match. | // of packages by prompting the user if multiple packages match. | ||||||
| func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string, interactive bool) []db.Package { | func FlattenPkgs(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool) []alrsh.Package { | ||||||
| 	log := loggerctx.From(ctx) | 	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 && interactive { | 		if len(pkgs) > 1 { | ||||||
|  | 			// Проверяем, являются ли пакеты подпакетами одного мультипакета | ||||||
|  | 			if isMultiPackage(pkgs) && verb == "install" { | ||||||
|  | 				// Для мультипакетов при установке ВСЕГДА берем все подпакеты без выбора | ||||||
|  | 				// Это правильное поведение как для прямой установки, так и для зависимостей | ||||||
|  | 				outPkgs = append(outPkgs, pkgs...) | ||||||
|  | 			} else if interactive { | ||||||
|  | 				// Для разных пакетов с одинаковым именем - показываем меню выбора | ||||||
| 				choice, err := PkgPrompt(ctx, pkgs, verb, interactive) | 				choice, err := PkgPrompt(ctx, pkgs, verb, interactive) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 				log.Fatal("Error prompting for choice of package").Send() | 					slog.Error(gotext.Get("Error prompting for choice of package")) | ||||||
|  | 					os.Exit(1) | ||||||
| 				} | 				} | ||||||
| 				outPkgs = append(outPkgs, choice) | 				outPkgs = append(outPkgs, choice) | ||||||
| 		} else if len(pkgs) == 1 || !interactive { | 			} else { | ||||||
|  | 				// Если не интерактивный режим - берем первый | ||||||
|  | 				outPkgs = append(outPkgs, pkgs[0]) | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			// Если только один пакет - берем его | ||||||
| 			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 []db.Package, verb string, interactive bool) (db.Package, error) { | func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, interactive bool) (alrsh.Package, error) { | ||||||
| 	if !interactive { | 	if !interactive { | ||||||
| 		return options[0], nil | 		return options[0], nil | ||||||
| 	} | 	} | ||||||
| @@ -133,13 +172,13 @@ func PkgPrompt(ctx context.Context, options []db.Package, verb string, interacti | |||||||
|  |  | ||||||
| 	prompt := &survey.Select{ | 	prompt := &survey.Select{ | ||||||
| 		Options: names, | 		Options: names, | ||||||
| 		Message: translations.Translator(ctx).TranslateTo("Choose which package to "+verb, config.Language(ctx)), | 		Message: gotext.Get("Choose which package to %s", verb), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var choice int | 	var choice int | ||||||
| 	err := survey.AskOne(prompt, &choice) | 	err := survey.AskOne(prompt, &choice) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return db.Package{}, err | 		return alrsh.Package{}, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return options[choice], nil | 	return options[choice], nil | ||||||
| @@ -154,7 +193,7 @@ func ChooseOptDepends(ctx context.Context, options []string, verb string, intera | |||||||
|  |  | ||||||
| 	prompt := &survey.MultiSelect{ | 	prompt := &survey.MultiSelect{ | ||||||
| 		Options: options, | 		Options: options, | ||||||
| 		Message: translations.Translator(ctx).TranslateTo("Choose which optional package(s) to install", config.Language(ctx)), | 		Message: gotext.Get("Choose which optional package(s) to install"), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var choices []int | 	var choices []int | ||||||
|   | |||||||
							
								
								
									
										102
									
								
								internal/cliutils/template.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								internal/cliutils/template.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package cliutils | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Templates are based on https://github.com/urfave/cli/blob/3b17080d70a630feadadd23dd036cad121dd9a50/template.go | ||||||
|  |  | ||||||
|  | //nolint:unused | ||||||
|  | var ( | ||||||
|  | 	helpNameTemplate    = `{{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}` | ||||||
|  | 	descriptionTemplate = `{{wrap .Description 3}}` | ||||||
|  | 	authorsTemplate     = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: | ||||||
|  |    {{range $index, $author := .Authors}}{{if $index}} | ||||||
|  |    {{end}}{{$author}}{{end}}` | ||||||
|  | 	visibleCommandTemplate = `{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} | ||||||
|  |    {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}` | ||||||
|  | 	visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}} | ||||||
|  |    {{.Name}}:{{range .VisibleCommands}} | ||||||
|  |      {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}` | ||||||
|  | 	visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}} | ||||||
|  |    {{if .Name}}{{.Name}} | ||||||
|  |  | ||||||
|  |    {{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}} | ||||||
|  | {{else}}{{$e}} | ||||||
|  |    {{end}}{{end}}{{end}}` | ||||||
|  | 	visibleFlagTemplate = `{{range $i, $e := .VisibleFlags}} | ||||||
|  |    {{wrap $e.String 6}}{{end}}` | ||||||
|  | 	copyrightTemplate = `{{wrap .Copyright 3}}` | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func GetAppCliTemplate() string { | ||||||
|  | 	return fmt.Sprintf(`%s: | ||||||
|  | 	{{template "helpNameTemplate" .}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  | 	{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[%s]{{end}}{{if .Commands}} %s [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  | 	{{.Version}}{{end}}{{end}}{{if .Description}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  |    {{template "descriptionTemplate" .}}{{end}} | ||||||
|  | {{- if len .Authors}} | ||||||
|  |  | ||||||
|  | %s{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} | ||||||
|  |  | ||||||
|  | %s:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}} | ||||||
|  |  | ||||||
|  | %s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} | ||||||
|  |  | ||||||
|  | %s:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  |    {{template "copyrightTemplate" .}}{{end}} | ||||||
|  | `, gotext.Get("NAME"), gotext.Get("USAGE"), gotext.Get("global options"), gotext.Get("command"), gotext.Get("command options"), gotext.Get("arguments"), gotext.Get("VERSION"), gotext.Get("DESCRIPTION"), gotext.Get("AUTHOR"), gotext.Get("COMMANDS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("COPYRIGHT")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetCommandHelpTemplate() string { | ||||||
|  | 	return fmt.Sprintf(`%s: | ||||||
|  |    {{template "helpNameTemplate" .}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  |    {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Category}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  |    {{.Category}}{{end}}{{if .Description}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  |    {{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}} | ||||||
|  |  | ||||||
|  | %s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} | ||||||
|  |  | ||||||
|  | %s:{{template "visibleFlagTemplate" .}}{{end}} | ||||||
|  | `, gotext.Get("NAME"), | ||||||
|  | 		gotext.Get("USAGE"), | ||||||
|  | 		gotext.Get("command options"), | ||||||
|  | 		gotext.Get("arguments"), | ||||||
|  | 		gotext.Get("CATEGORY"), | ||||||
|  | 		gotext.Get("DESCRIPTION"), | ||||||
|  | 		gotext.Get("OPTIONS"), | ||||||
|  | 		gotext.Get("OPTIONS"), | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								internal/cliutils/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								internal/cliutils/utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package cliutils | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log/slog" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  | 	"github.com/urfave/cli/v2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type BashCompleteWithErrorFunc func(c *cli.Context) error | ||||||
|  |  | ||||||
|  | func BashCompleteWithError(f BashCompleteWithErrorFunc) cli.BashCompleteFunc { | ||||||
|  | 	return func(c *cli.Context) { HandleExitCoder(f(c)) } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func HandleExitCoder(err error) { | ||||||
|  | 	if err == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if exitErr, ok := err.(cli.ExitCoder); ok { | ||||||
|  | 		if err.Error() != "" { | ||||||
|  | 			if _, ok := exitErr.(cli.ErrorFormatter); ok { | ||||||
|  | 				slog.Error(fmt.Sprintf("%+v\n", err)) | ||||||
|  | 			} else { | ||||||
|  | 				slog.Error(err.Error()) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		cli.OsExiter(exitErr.ExitCode()) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Error(err.Error()) | ||||||
|  | 	cli.OsExiter(1) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func FormatCliExit(msg string, err error) cli.ExitCoder { | ||||||
|  | 	return FormatCliExitWithCode(msg, err, 1) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func FormatCliExitWithCode(msg string, err error, exitCode int) cli.ExitCoder { | ||||||
|  | 	if err == nil { | ||||||
|  | 		return cli.Exit(errors.New(msg), exitCode) | ||||||
|  | 	} | ||||||
|  | 	return cli.Exit(fmt.Errorf("%s: %w", msg, err), exitCode) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func WarnLegacyCommand(newSyntax string) { | ||||||
|  | 	slog.Warn( | ||||||
|  | 		gotext.Get( | ||||||
|  | 			"This command is deprecated and would be removed in the future, use \"%s\" instead!", newSyntax, | ||||||
|  | 		), | ||||||
|  | 	) | ||||||
|  | } | ||||||
| @@ -1,79 +1,265 @@ | |||||||
| /* | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  * ALR - Any Linux Repository | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  * Copyright (C) 2024 Евгений Храмов | // | ||||||
|  * | // ALR - Any Linux Repository | ||||||
|  * This program is free software: you can redistribute it and/or modify | // Copyright (C) 2025 The ALR Authors | ||||||
|  * it under the terms of the GNU General Public License as published by | // | ||||||
|  * the Free Software Foundation, either version 3 of the License, or | // This program is free software: you can redistribute it and/or modify | ||||||
|  * (at your option) any later version. | // it under the terms of the GNU General Public License as published by | ||||||
|  * | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  * This program is distributed in the hope that it will be useful, | // (at your option) any later version. | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | // | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | // This program is distributed in the hope that it will be useful, | ||||||
|  * GNU General Public License for more details. | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  * | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  * You should have received a copy of the GNU General Public License | // GNU General Public License for more details. | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | // | ||||||
|  */ | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package config | package config | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"sync" | 	"os/exec" | ||||||
|  | 	"path/filepath" | ||||||
|  |  | ||||||
| 	"github.com/pelletier/go-toml/v2" | 	"github.com/goccy/go-yaml" | ||||||
| 	"plemya-x.ru/alr/internal/types" | 	"github.com/knadh/koanf/providers/confmap" | ||||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | 	"github.com/knadh/koanf/v2" | ||||||
|  | 	ktoml "github.com/knadh/koanf/parsers/toml/v2" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var defaultConfig = &types.Config{ | type ALRConfig struct { | ||||||
| 	RootCmd:          "sudo", | 	cfg   *types.Config | ||||||
| 	PagerStyle:       "native", | 	paths *Paths | ||||||
| 	IgnorePkgUpdates: []string{}, |  | ||||||
| 	Repos: []types.Repo{ | 	System *SystemConfig | ||||||
|  | 	env    *EnvConfig | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func New() *ALRConfig { | ||||||
|  | 	return &ALRConfig{ | ||||||
|  | 		System: NewSystemConfig(), | ||||||
|  | 		env:    NewEnvConfig(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func defaultConfigKoanf() *koanf.Koanf { | ||||||
|  | 	k := koanf.New(".") | ||||||
|  | 	defaults := map[string]interface{}{ | ||||||
|  | 		"rootCmd":          "sudo", | ||||||
|  | 		"useRootCmd":       true, | ||||||
|  | 		"pagerStyle":       "native", | ||||||
|  | 		"ignorePkgUpdates": []string{}, | ||||||
|  | 		"logLevel":         "info", | ||||||
|  | 		"autoPull":         true, | ||||||
|  | 		"updateSystemOnUpgrade": false, | ||||||
|  | 		"repos": []types.Repo{ | ||||||
| 			{ | 			{ | ||||||
| 			Name: "default", | 				Name: "alr-default", | ||||||
| 			URL:  "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git", | 				URL:  "https://gitea.plemya-x.ru/Plemya-x/alr-default.git", | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
|  | 	} | ||||||
|  | 	if err := k.Load(confmap.Provider(defaults, "."), nil); err != nil { | ||||||
|  | 		panic(k) | ||||||
|  | 	} | ||||||
|  | 	return k | ||||||
| } | } | ||||||
|  |  | ||||||
| var ( | func (c *ALRConfig) Load() error { | ||||||
| 	configMtx sync.Mutex | 	config := types.Config{} | ||||||
| 	config    *types.Config |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Config returns a ALR configuration struct. | 	merged := koanf.New(".") | ||||||
| // The first time it's called, it'll load the config from a file. |  | ||||||
| // Subsequent calls will just return the same value. |  | ||||||
| func Config(ctx context.Context) *types.Config { |  | ||||||
| 	configMtx.Lock() |  | ||||||
| 	defer configMtx.Unlock() |  | ||||||
| 	log := loggerctx.From(ctx) |  | ||||||
|  |  | ||||||
| 	if config == nil { | 	if err := c.System.Load(); err != nil { | ||||||
| 		cfgFl, err := os.Open(GetPaths(ctx).ConfigPath) | 		return fmt.Errorf("failed to load system config: %w", err) | ||||||
| 		if err != nil { |  | ||||||
| 			log.Warn("Error opening config file, using defaults").Err(err).Send() |  | ||||||
| 			return defaultConfig |  | ||||||
| 	} | 	} | ||||||
| 		defer cfgFl.Close() |  | ||||||
|  |  | ||||||
| 		// Copy the default configuration into config | 	if err := c.env.Load(); err != nil { | ||||||
| 		defCopy := *defaultConfig | 		return fmt.Errorf("failed to load env config: %w", err) | ||||||
| 		config = &defCopy | 	} | ||||||
| 		config.Repos = nil |  | ||||||
|  |  | ||||||
| 		err = toml.NewDecoder(cfgFl).Decode(config) | 	systemK := c.System.koanf() | ||||||
| 		if err != nil { | 	envK := c.env.koanf() | ||||||
| 			log.Warn("Error decoding config file, using defaults").Err(err).Send() |  | ||||||
| 			// Set config back to nil so that we try again next time | 	if err := merged.Merge(defaultConfigKoanf()); err != nil { | ||||||
| 			config = nil | 		return fmt.Errorf("failed to merge default config: %w", err) | ||||||
| 			return defaultConfig | 	} | ||||||
|  | 	if err := merged.Merge(systemK); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to merge system config: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := merged.Merge(envK); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to merge env config: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := merged.Unmarshal("", &config); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to unmarshal merged config: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.cfg = &config | ||||||
|  |  | ||||||
|  | 	c.paths = &Paths{} | ||||||
|  | 	c.paths.UserConfigPath = constants.SystemConfigPath | ||||||
|  | 	c.paths.CacheDir = constants.SystemCachePath | ||||||
|  | 	c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo") | ||||||
|  | 	c.paths.PkgsDir = filepath.Join(constants.TempDir, "pkgs")  // Перемещаем в /tmp/alr/pkgs | ||||||
|  | 	c.paths.DBPath = filepath.Join(c.paths.CacheDir, "alr.db") | ||||||
|  |  | ||||||
|  | 	// Проверяем существование кэш-директории, но не пытаемся создать | ||||||
|  | 	if _, err := os.Stat(c.paths.CacheDir); err != nil { | ||||||
|  | 		if !os.IsNotExist(err) { | ||||||
|  | 			return fmt.Errorf("failed to check cache directory: %w", err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return config | 	// Выполняем миграцию конфигурации при необходимости | ||||||
|  | 	if err := c.migrateConfig(); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to migrate config: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) ToYAML() (string, error) { | ||||||
|  | 	data, err := yaml.Marshal(c.cfg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return string(data), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) migrateConfig() error { | ||||||
|  | 	// Проверяем, существует ли конфигурационный файл | ||||||
|  | 	if _, err := os.Stat(constants.SystemConfigPath); os.IsNotExist(err) { | ||||||
|  | 		// Если файла нет, создаем полный конфигурационный файл с дефолтными значениями | ||||||
|  | 		if err := c.createDefaultConfig(); err != nil { | ||||||
|  | 			// Если не удается создать конфиг, это не критично - продолжаем работу | ||||||
|  | 			// но выводим предупреждение | ||||||
|  | 			fmt.Fprintf(os.Stderr, "Предупреждение: не удалось создать конфигурационный файл %s: %v\n", constants.SystemConfigPath, err) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		// Если файл существует, проверяем, есть ли в нем новая опция | ||||||
|  | 		if !c.System.k.Exists("updateSystemOnUpgrade") { | ||||||
|  | 			// Если опции нет, добавляем ее со значением по умолчанию | ||||||
|  | 			c.System.SetUpdateSystemOnUpgrade(false) | ||||||
|  | 			// Сохраняем обновленную конфигурацию | ||||||
|  | 			if err := c.System.Save(); err != nil { | ||||||
|  | 				// Если не удается сохранить - это не критично, продолжаем работу | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) createDefaultConfig() error { | ||||||
|  | 	// Проверяем, запущен ли процесс от root | ||||||
|  | 	if os.Getuid() != 0 { | ||||||
|  | 		// Если не root, пытаемся запустить создание конфига с повышением привилегий | ||||||
|  | 		return c.createDefaultConfigWithPrivileges() | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Если уже root, создаем конфиг напрямую | ||||||
|  | 	return c.doCreateDefaultConfig() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) createDefaultConfigWithPrivileges() error { | ||||||
|  | 	// Если useRootCmd отключен, просто пытаемся создать без повышения привилегий | ||||||
|  | 	if !c.cfg.UseRootCmd { | ||||||
|  | 		return c.doCreateDefaultConfig() | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Определяем команду для повышения привилегий | ||||||
|  | 	rootCmd := c.cfg.RootCmd | ||||||
|  | 	if rootCmd == "" { | ||||||
|  | 		rootCmd = "sudo" // fallback | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Создаем временный файл с дефолтной конфигурацией | ||||||
|  | 	tmpFile, err := os.CreateTemp("", "alr-config-*.toml") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("не удалось создать временный файл: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer os.Remove(tmpFile.Name()) | ||||||
|  | 	defer tmpFile.Close() | ||||||
|  | 	 | ||||||
|  | 	// Генерируем дефолтную конфигурацию во временный файл | ||||||
|  | 	defaults := defaultConfigKoanf() | ||||||
|  | 	tempSystemConfig := &SystemConfig{k: defaults} | ||||||
|  | 	 | ||||||
|  | 	bytes, err := tempSystemConfig.k.Marshal(ktoml.Parser()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("не удалось сериализовать конфигурацию: %w", err) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	if _, err := tmpFile.Write(bytes); err != nil { | ||||||
|  | 		return fmt.Errorf("не удалось записать во временный файл: %w", err) | ||||||
|  | 	} | ||||||
|  | 	tmpFile.Close() | ||||||
|  | 	 | ||||||
|  | 	// Используем команду повышения привилегий для создания директории и копирования файла | ||||||
|  | 	 | ||||||
|  | 	// Создаем директорию с правами | ||||||
|  | 	configDir := filepath.Dir(constants.SystemConfigPath) | ||||||
|  | 	mkdirCmd := exec.Command(rootCmd, "mkdir", "-p", configDir) | ||||||
|  | 	if err := mkdirCmd.Run(); err != nil { | ||||||
|  | 		return fmt.Errorf("не удалось создать директорию %s: %w", configDir, err) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Копируем файл в нужное место | ||||||
|  | 	cpCmd := exec.Command(rootCmd, "cp", tmpFile.Name(), constants.SystemConfigPath) | ||||||
|  | 	if err := cpCmd.Run(); err != nil { | ||||||
|  | 		return fmt.Errorf("не удалось скопировать конфигурацию в %s: %w", constants.SystemConfigPath, err) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Устанавливаем правильные права доступа | ||||||
|  | 	chmodCmd := exec.Command(rootCmd, "chmod", "644", constants.SystemConfigPath) | ||||||
|  | 	if err := chmodCmd.Run(); err != nil { | ||||||
|  | 		// Не критично, продолжаем | ||||||
|  | 		fmt.Fprintf(os.Stderr, "Предупреждение: не удалось установить права доступа для %s: %v\n", constants.SystemConfigPath, err) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) doCreateDefaultConfig() error { | ||||||
|  | 	// Проверяем, существует ли директория для конфига | ||||||
|  | 	configDir := filepath.Dir(constants.SystemConfigPath) | ||||||
|  | 	if _, err := os.Stat(configDir); os.IsNotExist(err) { | ||||||
|  | 		// Пытаемся создать директорию | ||||||
|  | 		if err := os.MkdirAll(configDir, 0755); err != nil { | ||||||
|  | 			return fmt.Errorf("не удалось создать директорию %s: %w", configDir, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Загружаем дефолтную конфигурацию | ||||||
|  | 	defaults := defaultConfigKoanf() | ||||||
|  | 	 | ||||||
|  | 	// Копируем все дефолтные значения в системную конфигурацию | ||||||
|  | 	c.System.k = defaults | ||||||
|  | 	 | ||||||
|  | 	// Сохраняем конфигурацию в файл | ||||||
|  | 	if err := c.System.Save(); err != nil { | ||||||
|  | 		return fmt.Errorf("не удалось сохранить конфигурацию в %s: %w", constants.SystemConfigPath, err) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) RootCmd() string             { return c.cfg.RootCmd } | ||||||
|  | func (c *ALRConfig) PagerStyle() string          { return c.cfg.PagerStyle } | ||||||
|  | func (c *ALRConfig) AutoPull() bool              { return c.cfg.AutoPull } | ||||||
|  | func (c *ALRConfig) Repos() []types.Repo         { return c.cfg.Repos } | ||||||
|  | func (c *ALRConfig) SetRepos(repos []types.Repo) { c.System.SetRepos(repos) } | ||||||
|  | func (c *ALRConfig) IgnorePkgUpdates() []string  { return c.cfg.IgnorePkgUpdates } | ||||||
|  | func (c *ALRConfig) LogLevel() string            { return c.cfg.LogLevel } | ||||||
|  | func (c *ALRConfig) UseRootCmd() bool            { return c.cfg.UseRootCmd } | ||||||
|  | func (c *ALRConfig) UpdateSystemOnUpgrade() bool { return c.cfg.UpdateSystemOnUpgrade } | ||||||
|  | func (c *ALRConfig) GetPaths() *Paths            { return c.paths } | ||||||
|   | |||||||
							
								
								
									
										76
									
								
								internal/config/env_config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								internal/config/env_config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package 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, "") | ||||||
|  | } | ||||||
| @@ -1,67 +0,0 @@ | |||||||
| /* |  | ||||||
|  * ALR - Any Linux Repository |  | ||||||
|  * Copyright (C) 2024 Евгений Храмов |  | ||||||
|  * |  | ||||||
|  * This program is free software: you can redistribute it and/or modify |  | ||||||
|  * it under the terms of the GNU General Public License as published by |  | ||||||
|  * the Free Software Foundation, either version 3 of the License, or |  | ||||||
|  * (at your option) any later version. |  | ||||||
|  * |  | ||||||
|  * This program is distributed in the hope that it will be useful, |  | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|  * GNU General Public License for more details. |  | ||||||
|  * |  | ||||||
|  * You should have received a copy of the GNU General Public License |  | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package config |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"os" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync" |  | ||||||
|  |  | ||||||
| 	"plemya-x.ru/alr/pkg/loggerctx" |  | ||||||
| 	"golang.org/x/text/language" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	langMtx sync.Mutex |  | ||||||
| 	lang    language.Tag |  | ||||||
| 	langSet bool |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Language returns the system language. |  | ||||||
| // The first time it's called, it'll detect the langauge based on |  | ||||||
| // the $LANG environment variable. |  | ||||||
| // Subsequent calls will just return the same value. |  | ||||||
| func Language(ctx context.Context) language.Tag { |  | ||||||
| 	langMtx.Lock() |  | ||||||
| 	defer langMtx.Unlock() |  | ||||||
| 	log := loggerctx.From(ctx) |  | ||||||
| 	if !langSet { |  | ||||||
| 		syslang := SystemLang() |  | ||||||
| 		tag, err := language.Parse(syslang) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Fatal("Error parsing system language").Err(err).Send() |  | ||||||
| 		} |  | ||||||
| 		base, _ := tag.Base() |  | ||||||
| 		lang = language.Make(base.String()) |  | ||||||
| 		langSet = true |  | ||||||
| 	} |  | ||||||
| 	return lang |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SystemLang returns the system language based on |  | ||||||
| // the $LANG environment variable. |  | ||||||
| func SystemLang() string { |  | ||||||
| 	lang := os.Getenv("LANG") |  | ||||||
| 	lang, _, _ = strings.Cut(lang, ".") |  | ||||||
| 	if lang == "" || lang == "C" { |  | ||||||
| 		lang = "en" |  | ||||||
| 	} |  | ||||||
| 	return lang |  | ||||||
| } |  | ||||||
| @@ -1,108 +1,30 @@ | |||||||
| /* | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  * ALR - Any Linux Repository | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  * Copyright (C) 2024 Евгений Храмов | // | ||||||
|  * | // ALR - Any Linux Repository | ||||||
|  * This program is free software: you can redistribute it and/or modify | // Copyright (C) 2025 The ALR Authors | ||||||
|  * it under the terms of the GNU General Public License as published by | // | ||||||
|  * the Free Software Foundation, either version 3 of the License, or | // This program is free software: you can redistribute it and/or modify | ||||||
|  * (at your option) any later version. | // it under the terms of the GNU General Public License as published by | ||||||
|  * | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  * This program is distributed in the hope that it will be useful, | // (at your option) any later version. | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | // | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | // This program is distributed in the hope that it will be useful, | ||||||
|  * GNU General Public License for more details. | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  * | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  * You should have received a copy of the GNU General Public License | // GNU General Public License for more details. | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | // | ||||||
|  */ | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package config | package config | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"sync" |  | ||||||
|  |  | ||||||
| 	"github.com/pelletier/go-toml/v2" |  | ||||||
| 	"plemya-x.ru/alr/pkg/loggerctx" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Paths contains various paths used by ALR | // Paths contains various paths used by ALR | ||||||
| type Paths struct { | type Paths struct { | ||||||
| 	ConfigDir  string | 	SystemConfigPath string | ||||||
| 	ConfigPath string | 	UserConfigPath   string | ||||||
| 	CacheDir         string | 	CacheDir         string | ||||||
| 	RepoDir          string | 	RepoDir          string | ||||||
| 	PkgsDir          string | 	PkgsDir          string | ||||||
| 	DBPath           string | 	DBPath           string | ||||||
| } | } | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	pathsMtx sync.Mutex |  | ||||||
| 	paths    *Paths |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // GetPaths returns a Paths struct. |  | ||||||
| // The first time it's called, it'll generate the struct |  | ||||||
| // using information from the system. |  | ||||||
| // Subsequent calls will return the same value. |  | ||||||
| func GetPaths(ctx context.Context) *Paths { |  | ||||||
| 	pathsMtx.Lock() |  | ||||||
| 	defer pathsMtx.Unlock() |  | ||||||
|  |  | ||||||
| 	log := loggerctx.From(ctx) |  | ||||||
| 	if paths == nil { |  | ||||||
| 		paths = &Paths{} |  | ||||||
|  |  | ||||||
| 		cfgDir, err := os.UserConfigDir() |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Fatal("Unable to detect user config directory").Err(err).Send() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		paths.ConfigDir = filepath.Join(cfgDir, "alr") |  | ||||||
|  |  | ||||||
| 		err = os.MkdirAll(paths.ConfigDir, 0o755) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Fatal("Unable to create ALR config directory").Err(err).Send() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		paths.ConfigPath = filepath.Join(paths.ConfigDir, "alr.toml") |  | ||||||
|  |  | ||||||
| 		if _, err := os.Stat(paths.ConfigPath); err != nil { |  | ||||||
| 			cfgFl, err := os.Create(paths.ConfigPath) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Fatal("Unable to create ALR config file").Err(err).Send() |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			err = toml.NewEncoder(cfgFl).Encode(&defaultConfig) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Fatal("Error encoding default configuration").Err(err).Send() |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			cfgFl.Close() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		cacheDir, err := os.UserCacheDir() |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Fatal("Unable to detect cache directory").Err(err).Send() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		paths.CacheDir = filepath.Join(cacheDir, "alr") |  | ||||||
| 		paths.RepoDir = filepath.Join(paths.CacheDir, "repo") |  | ||||||
| 		paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs") |  | ||||||
|  |  | ||||||
| 		err = os.MkdirAll(paths.RepoDir, 0o755) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Fatal("Unable to create repo cache directory").Err(err).Send() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		err = os.MkdirAll(paths.PkgsDir, 0o755) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Fatal("Unable to create package cache directory").Err(err).Send() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		paths.DBPath = filepath.Join(paths.CacheDir, "db") |  | ||||||
| 	} |  | ||||||
| 	return paths |  | ||||||
| } |  | ||||||
|   | |||||||
							
								
								
									
										151
									
								
								internal/config/system_config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								internal/config/system_config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | // 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) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,3 +1,22 @@ | |||||||
|  | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  | // | ||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package config | package config | ||||||
|  |  | ||||||
| // Version contains the version of ALR. If the version | // Version contains the version of ALR. If the version | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								internal/constants/constants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								internal/constants/constants.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package constants | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	SystemConfigPath = "/etc/alr/alr.toml" | ||||||
|  | 	SystemCachePath  = "/var/cache/alr" | ||||||
|  | 	TempDir          = "/tmp/alr" | ||||||
|  | 	// PrivilegedGroup - устарело, используйте GetPrivilegedGroup() | ||||||
|  | 	PrivilegedGroup  = "wheel" // оставлено для обратной совместимости | ||||||
|  | ) | ||||||
| @@ -1,20 +1,21 @@ | |||||||
| /* | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  * ALR - Any Linux Repository | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  * Copyright (C) 2024 Евгений Храмов | // | ||||||
|  * | // ALR - Any Linux Repository | ||||||
|  * This program is free software: you can redistribute it and/or modify | // Copyright (C) 2025 The ALR Authors | ||||||
|  * it under the terms of the GNU General Public License as published by | // | ||||||
|  * the Free Software Foundation, either version 3 of the License, or | // This program is free software: you can redistribute it and/or modify | ||||||
|  * (at your option) any later version. | // it under the terms of the GNU General Public License as published by | ||||||
|  * | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  * This program is distributed in the hope that it will be useful, | // (at your option) any later version. | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | // | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | // This program is distributed in the hope that it will be useful, | ||||||
|  * GNU General Public License for more details. | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  * | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  * You should have received a copy of the GNU General Public License | // GNU General Public License for more details. | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | // | ||||||
|  */ | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package cpu | package cpu | ||||||
|  |  | ||||||
| @@ -37,11 +38,12 @@ func armVariant() string { | |||||||
| 		return armEnv | 		return armEnv | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if cpu.ARM.HasVFPv3 { | 	switch { | ||||||
|  | 	case cpu.ARM.HasVFPv3: | ||||||
| 		return "arm7" | 		return "arm7" | ||||||
| 	} else if cpu.ARM.HasVFP { | 	case cpu.ARM.HasVFP: | ||||||
| 		return "arm6" | 		return "arm6" | ||||||
| 	} else { | 	default: | ||||||
| 		return "arm5" | 		return "arm5" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,346 +1,167 @@ | |||||||
| /* | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  * ALR - Any Linux Repository | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  * Copyright (C) 2024 Евгений Храмов | // | ||||||
|  * | // ALR - Any Linux Repository | ||||||
|  * This program is free software: you can redistribute it and/or modify | // Copyright (C) 2025 The ALR Authors | ||||||
|  * it under the terms of the GNU General Public License as published by | // | ||||||
|  * the Free Software Foundation, either version 3 of the License, or | // This program is free software: you can redistribute it and/or modify | ||||||
|  * (at your option) any later version. | // it under the terms of the GNU General Public License as published by | ||||||
|  * | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  * This program is distributed in the hope that it will be useful, | // (at your option) any later version. | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | // | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | // This program is distributed in the hope that it will be useful, | ||||||
|  * GNU General Public License for more details. | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  * | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  * You should have received a copy of the GNU General Public License | // GNU General Public License for more details. | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | // | ||||||
|  */ | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package db | package db | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"database/sql" |  | ||||||
| 	"database/sql/driver" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"sync" | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  |  | ||||||
| 	"github.com/jmoiron/sqlx" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"plemya-x.ru/alr/internal/config" | 	_ "modernc.org/sqlite" | ||||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | 	"xorm.io/xorm" | ||||||
| 	"golang.org/x/exp/slices" |  | ||||||
| 	"modernc.org/sqlite" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // CurrentVersion is the current version of the database. | const CurrentVersion = 5 | ||||||
| // The database is reset if its version doesn't match this. |  | ||||||
| const CurrentVersion = 2 |  | ||||||
|  |  | ||||||
| func init() { | type Version struct { | ||||||
| 	sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains) | 	Version int `xorm:"'version'"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // Package is a ALR package's database representation | type Config interface { | ||||||
| type Package struct { | 	GetPaths() *config.Paths | ||||||
| 	Name          string                    `sh:"name,required" db:"name"` |  | ||||||
| 	Version       string                    `sh:"version,required" db:"version"` |  | ||||||
| 	Release       int                       `sh:"release,required" db:"release"` |  | ||||||
| 	Epoch         uint                      `sh:"epoch" db:"epoch"` |  | ||||||
| 	Description   JSON[map[string]string]   `db:"description"` |  | ||||||
| 	Homepage      JSON[map[string]string]   `db:"homepage"` |  | ||||||
| 	Maintainer    JSON[map[string]string]   `db:"maintainer"` |  | ||||||
| 	Architectures JSON[[]string]            `sh:"architectures" db:"architectures"` |  | ||||||
| 	Licenses      JSON[[]string]            `sh:"license" db:"licenses"` |  | ||||||
| 	Provides      JSON[[]string]            `sh:"provides" db:"provides"` |  | ||||||
| 	Conflicts     JSON[[]string]            `sh:"conflicts" db:"conflicts"` |  | ||||||
| 	Replaces      JSON[[]string]            `sh:"replaces" db:"replaces"` |  | ||||||
| 	Depends       JSON[map[string][]string] `db:"depends"` |  | ||||||
| 	BuildDepends  JSON[map[string][]string] `db:"builddepends"` |  | ||||||
| 	OptDepends    JSON[map[string][]string] `db:"optdepends"` |  | ||||||
| 	Repository    string                    `db:"repository"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type version struct { | type Database struct { | ||||||
| 	Version int `db:"version"` | 	engine *xorm.Engine | ||||||
|  | 	config Config | ||||||
| } | } | ||||||
|  |  | ||||||
| var ( | func New(config Config) *Database { | ||||||
| 	mu     sync.Mutex | 	return &Database{ | ||||||
| 	conn   *sqlx.DB | 		config: config, | ||||||
| 	closed = true |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // DB returns the ALR database. |  | ||||||
| // The first time it's called, it opens the SQLite database file. |  | ||||||
| // Subsequent calls return the same connection. |  | ||||||
| func DB(ctx context.Context) *sqlx.DB { |  | ||||||
| 	log := loggerctx.From(ctx) |  | ||||||
| 	if conn != nil && !closed { |  | ||||||
| 		return getConn() |  | ||||||
| 	} | 	} | ||||||
| 	_, err := open(ctx, config.GetPaths(ctx).DBPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Fatal("Error opening database").Err(err).Send() |  | ||||||
| 	} |  | ||||||
| 	return getConn() |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func getConn() *sqlx.DB { | func (d *Database) Connect() error { | ||||||
| 	mu.Lock() | 	dsn := d.config.GetPaths().DBPath | ||||||
| 	defer mu.Unlock() |  | ||||||
| 	return conn |  | ||||||
| } |  | ||||||
| 	 | 	 | ||||||
| func open(ctx context.Context, dsn string) (*sqlx.DB, error) { | 	// Проверяем директорию для БД | ||||||
| 	db, err := sqlx.Open("sqlite", dsn) | 	dbDir := filepath.Dir(dsn) | ||||||
| 	if err != nil { | 	if _, err := os.Stat(dbDir); err != nil { | ||||||
| 		return nil, err | 		if os.IsNotExist(err) { | ||||||
| 	} | 			// Директория не существует - не пытаемся создать | ||||||
|  | 			// Пользователь должен использовать alr fix для создания системных каталогов | ||||||
| 	mu.Lock() | 			return fmt.Errorf("cache directory does not exist, please run 'sudo alr fix' to create it") | ||||||
| 	conn = db |  | ||||||
| 	closed = false |  | ||||||
| 	mu.Unlock() |  | ||||||
|  |  | ||||||
| 	err = initDB(ctx, dsn) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return db, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Close closes the database |  | ||||||
| func Close() error { |  | ||||||
| 	closed = true |  | ||||||
| 	if conn != nil { |  | ||||||
| 		return conn.Close() |  | ||||||
| 		} else { | 		} else { | ||||||
| 		return nil | 			return fmt.Errorf("failed to check database directory: %w", err) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } |  | ||||||
| 	 | 	 | ||||||
| // initDB initializes the database | 	engine, err := xorm.NewEngine("sqlite", dsn) | ||||||
| func initDB(ctx context.Context, dsn string) error { | 	// engine.SetLogLevel(log.LOG_DEBUG) | ||||||
| 	log := loggerctx.From(ctx) | 	// engine.ShowSQL(true) | ||||||
| 	conn = conn.Unsafe() |  | ||||||
| 	_, err := conn.ExecContext(ctx, ` |  | ||||||
| 		CREATE TABLE IF NOT EXISTS pkgs ( |  | ||||||
| 			name          TEXT NOT NULL, |  | ||||||
| 			repository    TEXT NOT NULL, |  | ||||||
| 			version       TEXT NOT NULL, |  | ||||||
| 			release       INT  NOT NULL, |  | ||||||
| 			epoch         INT, |  | ||||||
| 			description   TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')), |  | ||||||
| 			homepage      TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')), |  | ||||||
| 			maintainer    TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')), |  | ||||||
| 			architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')), |  | ||||||
| 			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 { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	d.engine = engine | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| 	ver, ok := GetVersion(ctx) | func (d *Database) Init(ctx context.Context) error { | ||||||
|  | 	if err := d.Connect(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := d.engine.Sync2(new(alrsh.Package), new(Version)); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	ver, ok := d.GetVersion(ctx) | ||||||
| 	if ok && ver != CurrentVersion { | 	if ok && ver != CurrentVersion { | ||||||
| 		log.Warn("Database version mismatch; resetting").Int("version", ver).Int("expected", CurrentVersion).Send() | 		slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion) | ||||||
| 		reset(ctx) | 		if err := d.reset(); err != nil { | ||||||
| 		return initDB(ctx, dsn) | 			return err | ||||||
| 	} else if !ok { | 		} | ||||||
| 		log.Warn("Database version does not exist. Run alr fix if something isn't working.").Send() | 		return d.Init(ctx) | ||||||
| 		return addVersion(ctx, CurrentVersion) | 	} else if !ok { | ||||||
|  | 		slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working.")) | ||||||
|  | 		return d.addVersion(CurrentVersion) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // reset drops all the database tables | func (d *Database) GetVersion(ctx context.Context) (int, bool) { | ||||||
| func reset(ctx context.Context) error { | 	var v Version | ||||||
| 	_, err := DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;") | 	has, err := d.engine.Get(&v) | ||||||
| 	if err != nil { | 	if err != nil || !has { | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	_, err = DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS alr_db_version;") |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // IsEmpty returns true if the database has no packages in it, otherwise it returns false. |  | ||||||
| func IsEmpty(ctx context.Context) bool { |  | ||||||
| 	var count int |  | ||||||
| 	err := DB(ctx).GetContext(ctx, &count, "SELECT count(1) FROM pkgs;") |  | ||||||
| 	if err != nil { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return count == 0 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetVersion returns the database version and a boolean indicating |  | ||||||
| // whether the database contained a version number |  | ||||||
| func GetVersion(ctx context.Context) (int, bool) { |  | ||||||
| 	var ver version |  | ||||||
| 	err := DB(ctx).GetContext(ctx, &ver, "SELECT * FROM alr_db_version LIMIT 1;") |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0, false | 		return 0, false | ||||||
| 	} | 	} | ||||||
| 	return ver.Version, true | 	return v.Version, true | ||||||
| } | } | ||||||
|  |  | ||||||
| func addVersion(ctx context.Context, ver int) error { | func (d *Database) addVersion(ver int) error { | ||||||
| 	_, err := DB(ctx).ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver) | 	_, err := d.engine.Insert(&Version{Version: ver}) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| // InsertPackage adds a package to the database | func (d *Database) reset() error { | ||||||
| func InsertPackage(ctx context.Context, pkg Package) error { | 	return d.engine.DropTables(new(alrsh.Package), new(Version)) | ||||||
| 	_, err := DB(ctx).NamedExecContext(ctx, ` |  | ||||||
| 		INSERT OR REPLACE INTO pkgs ( |  | ||||||
| 			name, |  | ||||||
| 			repository, |  | ||||||
| 			version, |  | ||||||
| 			release, |  | ||||||
| 			epoch, |  | ||||||
| 			description, |  | ||||||
| 			homepage, |  | ||||||
| 			maintainer, |  | ||||||
| 			architectures, |  | ||||||
| 			licenses, |  | ||||||
| 			provides, |  | ||||||
| 			conflicts, |  | ||||||
| 			replaces, |  | ||||||
| 			depends, |  | ||||||
| 			builddepends, |  | ||||||
| 			optdepends |  | ||||||
| 		) VALUES ( |  | ||||||
| 			:name, |  | ||||||
| 			:repository, |  | ||||||
| 			:version, |  | ||||||
| 			:release, |  | ||||||
| 			:epoch, |  | ||||||
| 			:description, |  | ||||||
| 			:homepage, |  | ||||||
| 			:maintainer, |  | ||||||
| 			:architectures, |  | ||||||
| 			:licenses, |  | ||||||
| 			:provides, |  | ||||||
| 			:conflicts, |  | ||||||
| 			:replaces, |  | ||||||
| 			:depends, |  | ||||||
| 			:builddepends, |  | ||||||
| 			:optdepends |  | ||||||
| 		); |  | ||||||
| 	`, pkg) |  | ||||||
| 	return err |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetPkgs returns a result containing packages that match the where conditions | func (d *Database) InsertPackage(ctx context.Context, pkg alrsh.Package) error { | ||||||
| func GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) { | 	session := d.engine.Context(ctx) | ||||||
| 	stream, err := DB(ctx).QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return stream, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetPkg returns a single package that matches the where conditions | 	affected, err := session.Where("name = ? AND repository = ?", pkg.Name, pkg.Repository).Update(&pkg) | ||||||
| func GetPkg(ctx context.Context, where string, args ...any) (*Package, error) { |  | ||||||
| 	out := &Package{} |  | ||||||
| 	err := DB(ctx).GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...) |  | ||||||
| 	return out, err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // DeletePkgs deletes all packages matching the where conditions |  | ||||||
| func DeletePkgs(ctx context.Context, where string, args ...any) error { |  | ||||||
| 	_, err := DB(ctx).ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...) |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // jsonArrayContains is an SQLite function that checks if a JSON array |  | ||||||
| // in the database contains a given value |  | ||||||
| func jsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) { |  | ||||||
| 	value, ok := args[0].(string) |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil, errors.New("both arguments to json_array_contains must be strings") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	item, ok := args[1].(string) |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil, errors.New("both arguments to json_array_contains must be strings") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var array []string |  | ||||||
| 	err := json.Unmarshal([]byte(value), &array) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return slices.Contains(array, item), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // JSON represents a JSON value in the database |  | ||||||
| type JSON[T any] struct { |  | ||||||
| 	Val T |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewJSON creates a new database JSON value |  | ||||||
| func NewJSON[T any](v T) JSON[T] { |  | ||||||
| 	return JSON[T]{Val: v} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *JSON[T]) Scan(val any) error { |  | ||||||
| 	if val == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	switch val := val.(type) { |  | ||||||
| 	case string: |  | ||||||
| 		err := json.Unmarshal([]byte(val), &s.Val) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	case sql.NullString: |  | ||||||
| 		if val.Valid { | 	if affected == 0 { | ||||||
| 			err := json.Unmarshal([]byte(val.String), &s.Val) | 		_, err = session.Insert(&pkg) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	default: |  | ||||||
| 		return errors.New("sqlite json types must be strings") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s JSON[T]) Value() (driver.Value, error) { | func (d *Database) GetPkgs(_ context.Context, where string, args ...any) ([]alrsh.Package, error) { | ||||||
| 	data, err := json.Marshal(s.Val) | 	var pkgs []alrsh.Package | ||||||
| 	if err != nil { | 	err := d.engine.Where(where, args...).Find(&pkgs) | ||||||
|  | 	return pkgs, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Database) GetPkg(where string, args ...any) (*alrsh.Package, error) { | ||||||
|  | 	var pkg alrsh.Package | ||||||
|  | 	has, err := d.engine.Where(where, args...).Get(&pkg) | ||||||
|  | 	if err != nil || !has { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return string(data), nil | 	return &pkg, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s JSON[T]) MarshalYAML() (any, error) { | func (d *Database) DeletePkgs(_ context.Context, where string, args ...any) error { | ||||||
| 	return s.Val, nil | 	_, err := d.engine.Where(where, args...).Delete(&alrsh.Package{}) | ||||||
|  | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s JSON[T]) String() string { | func (d *Database) IsEmpty() bool { | ||||||
| 	return fmt.Sprint(s.Val) | 	count, err := d.engine.Count(new(alrsh.Package)) | ||||||
|  | 	return err != nil || count == 0 | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s JSON[T]) GoString() string { | func (d *Database) Close() error { | ||||||
| 	return fmt.Sprintf("%#v", s.Val) | 	return d.engine.Close() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,76 +1,91 @@ | |||||||
| /* | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  * ALR - Any Linux Repository | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  * Copyright (C) 2024 Евгений Храмов | // | ||||||
|  * | // ALR - Any Linux Repository | ||||||
|  * This program is free software: you can redistribute it and/or modify | // Copyright (C) 2025 The ALR Authors | ||||||
|  * it under the terms of the GNU General Public License as published by | // | ||||||
|  * the Free Software Foundation, either version 3 of the License, or | // This program is free software: you can redistribute it and/or modify | ||||||
|  * (at your option) any later version. | // it under the terms of the GNU General Public License as published by | ||||||
|  * | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  * This program is distributed in the hope that it will be useful, | // (at your option) any later version. | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | // | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | // This program is distributed in the hope that it will be useful, | ||||||
|  * GNU General Public License for more details. | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  * | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  * You should have received a copy of the GNU General Public License | // GNU General Public License for more details. | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | // | ||||||
|  */ | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| package db_test | package db_test | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/jmoiron/sqlx" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"plemya-x.ru/alr/internal/db" |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var testPkg = db.Package{ | type TestALRConfig struct{} | ||||||
|  |  | ||||||
|  | func (c *TestALRConfig) GetPaths() *config.Paths { | ||||||
|  | 	return &config.Paths{ | ||||||
|  | 		DBPath: ":memory:", | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func prepareDb() *db.Database { | ||||||
|  | 	database := db.New(&TestALRConfig{}) | ||||||
|  | 	database.Init(context.Background()) | ||||||
|  | 	return database | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var testPkg = alrsh.Package{ | ||||||
| 	Name:    "test", | 	Name:    "test", | ||||||
| 	Version: "0.0.1", | 	Version: "0.0.1", | ||||||
| 	Release: 1, | 	Release: 1, | ||||||
| 	Epoch:   2, | 	Epoch:   2, | ||||||
| 	Description: db.NewJSON(map[string]string{ | 	Description: alrsh.OverridableFromMap(map[string]string{ | ||||||
| 		"en": "Test package", | 		"en": "Test package", | ||||||
| 		"ru": "Проверочный пакет", | 		"ru": "Проверочный пакет", | ||||||
| 	}), | 	}), | ||||||
| 	Homepage: db.NewJSON(map[string]string{ | 	Homepage: alrsh.OverridableFromMap(map[string]string{ | ||||||
| 		"en": "https://gitea.plemya-x.ru/xpamych/ALR", | 		"en": "https://gitea.plemya-x.ru/xpamych/ALR", | ||||||
| 	}), | 	}), | ||||||
| 	Maintainer: db.NewJSON(map[string]string{ | 	Maintainer: alrsh.OverridableFromMap(map[string]string{ | ||||||
| 		"en": "Evgeniy Khramov <xpamych@yandex.ru>", | 		"en": "Evgeniy Khramov <xpamych@yandex.ru>", | ||||||
| 		"ru": "Евгений Храмов <xpamych@yandex.ru>", | 		"ru": "Евгений Храмов <xpamych@yandex.ru>", | ||||||
| 	}), | 	}), | ||||||
| 	Architectures: db.NewJSON([]string{"arm64", "amd64"}), | 	Architectures: []string{"arm64", "amd64"}, | ||||||
| 	Licenses:      db.NewJSON([]string{"GPL-3.0-or-later"}), | 	Licenses:      []string{"GPL-3.0-or-later"}, | ||||||
| 	Provides:      db.NewJSON([]string{"test"}), | 	Provides:      []string{"test"}, | ||||||
| 	Conflicts:     db.NewJSON([]string{"test"}), | 	Conflicts:     []string{"test"}, | ||||||
| 	Replaces:      db.NewJSON([]string{"test-old"}), | 	Replaces:      []string{"test-old"}, | ||||||
| 	Depends: db.NewJSON(map[string][]string{ | 	Depends: alrsh.OverridableFromMap(map[string][]string{ | ||||||
| 		"": {"sudo"}, | 		"": {"sudo"}, | ||||||
| 	}), | 	}), | ||||||
| 	BuildDepends: db.NewJSON(map[string][]string{ | 	BuildDepends: alrsh.OverridableFromMap(map[string][]string{ | ||||||
| 		"":     {"golang"}, | 		"":     {"golang"}, | ||||||
| 		"arch": {"go"}, | 		"arch": {"go"}, | ||||||
| 	}), | 	}), | ||||||
| 	Repository: "default", | 	Repository: "default", | ||||||
|  | 	Summary:    alrsh.OverridableFromMap(map[string]string{}), | ||||||
|  | 	Group:      alrsh.OverridableFromMap(map[string]string{}), | ||||||
|  | 	OptDepends: alrsh.OverridableFromMap(map[string][]string{}), | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestInit(t *testing.T) { | func TestInit(t *testing.T) { | ||||||
| 	_, err := db.Open(":memory:") | 	ctx := context.Background() | ||||||
| 	if err != nil { | 	database := prepareDb() | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 	defer database.Close() | ||||||
| 	} |  | ||||||
| 	defer db.Close() |  | ||||||
|  |  | ||||||
| 	_, err = db.DB().Exec("SELECT * FROM pkgs") | 	ver, ok := database.GetVersion(ctx) | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("Expected no error, got %s", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ver, ok := db.GetVersion() |  | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		t.Errorf("Expected version to be present") | 		t.Errorf("Expected version to be present") | ||||||
| 	} else if ver != db.CurrentVersion { | 	} else if ver != db.CurrentVersion { | ||||||
| @@ -79,62 +94,53 @@ func TestInit(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestInsertPackage(t *testing.T) { | func TestInsertPackage(t *testing.T) { | ||||||
| 	_, err := db.Open(":memory:") | 	ctx := context.Background() | ||||||
| 	if err != nil { | 	database := prepareDb() | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 	defer database.Close() | ||||||
| 	} |  | ||||||
| 	defer db.Close() |  | ||||||
|  |  | ||||||
| 	err = db.InsertPackage(testPkg) | 	err := database.InsertPackage(ctx, testPkg) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	dbPkg := db.Package{} | 	pkgs, err := database.GetPkgs(ctx, "name = 'test' AND repository = 'default'") | ||||||
| 	err = sqlx.Get(db.DB(), &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'") |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !reflect.DeepEqual(testPkg, dbPkg) { | 	if len(pkgs) != 1 { | ||||||
| 		t.Errorf("Expected test package to be the same as database package") | 		t.Fatalf("Expected 1 package, got %d", len(pkgs)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	assert.Equal(t, testPkg, pkgs[0]) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestGetPkgs(t *testing.T) { | func TestGetPkgs(t *testing.T) { | ||||||
| 	_, err := db.Open(":memory:") | 	ctx := context.Background() | ||||||
| 	if err != nil { | 	database := prepareDb() | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 	defer database.Close() | ||||||
| 	} |  | ||||||
| 	defer db.Close() |  | ||||||
|  |  | ||||||
| 	x1 := testPkg | 	x1 := testPkg | ||||||
| 	x1.Name = "x1" | 	x1.Name = "x1" | ||||||
| 	x2 := testPkg | 	x2 := testPkg | ||||||
| 	x2.Name = "x2" | 	x2.Name = "x2" | ||||||
|  |  | ||||||
| 	err = db.InsertPackage(x1) | 	err := database.InsertPackage(ctx, x1) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err = db.InsertPackage(x2) | 	err = database.InsertPackage(ctx, x2) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	result, err := db.GetPkgs("name LIKE 'x%'") | 	pkgs, err := database.GetPkgs(ctx, "name LIKE 'x%'") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for result.Next() { | 	for _, dbPkg := range pkgs { | ||||||
| 		var dbPkg db.Package |  | ||||||
| 		err = result.StructScan(&dbPkg) |  | ||||||
| 		if err != nil { |  | ||||||
| 			t.Errorf("Expected no error, got %s", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !strings.HasPrefix(dbPkg.Name, "x") { | 		if !strings.HasPrefix(dbPkg.Name, "x") { | ||||||
| 			t.Errorf("Expected package name to start with 'x', got %s", dbPkg.Name) | 			t.Errorf("Expected package name to start with 'x', got %s", dbPkg.Name) | ||||||
| 		} | 		} | ||||||
| @@ -142,28 +148,26 @@ func TestGetPkgs(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestGetPkg(t *testing.T) { | func TestGetPkg(t *testing.T) { | ||||||
| 	_, err := db.Open(":memory:") | 	ctx := context.Background() | ||||||
| 	if err != nil { | 	database := prepareDb() | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 	defer database.Close() | ||||||
| 	} |  | ||||||
| 	defer db.Close() |  | ||||||
|  |  | ||||||
| 	x1 := testPkg | 	x1 := testPkg | ||||||
| 	x1.Name = "x1" | 	x1.Name = "x1" | ||||||
| 	x2 := testPkg | 	x2 := testPkg | ||||||
| 	x2.Name = "x2" | 	x2.Name = "x2" | ||||||
|  |  | ||||||
| 	err = db.InsertPackage(x1) | 	err := database.InsertPackage(ctx, x1) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err = db.InsertPackage(x2) | 	err = database.InsertPackage(ctx, x2) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pkg, err := db.GetPkg("name LIKE 'x%' ORDER BY name") | 	pkg, err := database.GetPkg("name LIKE 'x%'") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
| @@ -178,73 +182,70 @@ func TestGetPkg(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestDeletePkgs(t *testing.T) { | func TestDeletePkgs(t *testing.T) { | ||||||
| 	_, err := db.Open(":memory:") | 	ctx := context.Background() | ||||||
| 	if err != nil { | 	database := prepareDb() | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 	defer database.Close() | ||||||
| 	} |  | ||||||
| 	defer db.Close() |  | ||||||
|  |  | ||||||
| 	x1 := testPkg | 	x1 := testPkg | ||||||
| 	x1.Name = "x1" | 	x1.Name = "x1" | ||||||
| 	x2 := testPkg | 	x2 := testPkg | ||||||
| 	x2.Name = "x2" | 	x2.Name = "x2" | ||||||
|  |  | ||||||
| 	err = db.InsertPackage(x1) | 	err := database.InsertPackage(ctx, x1) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err = db.InsertPackage(x2) | 	err = database.InsertPackage(ctx, x2) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err = db.DeletePkgs("name = 'x1'") | 	err = database.DeletePkgs(ctx, "name = 'x1'") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var dbPkg db.Package |  | ||||||
| 	err = db.DB().Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;") |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("Expected no error, got %s", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if dbPkg.Name != "x2" { |  | ||||||
| 		t.Errorf("Expected x2 package, got %s", dbPkg.Name) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestJsonArrayContains(t *testing.T) { | func TestJsonArrayContains(t *testing.T) { | ||||||
| 	_, err := db.Open(":memory:") | 	ctx := context.Background() | ||||||
| 	if err != nil { | 	database := prepareDb() | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 	defer database.Close() | ||||||
| 	} |  | ||||||
| 	defer db.Close() |  | ||||||
|  |  | ||||||
| 	x1 := testPkg | 	x1 := testPkg | ||||||
| 	x1.Name = "x1" | 	x1.Name = "x1" | ||||||
| 	x2 := testPkg | 	x2 := testPkg | ||||||
| 	x2.Name = "x2" | 	x2.Name = "x2" | ||||||
| 	x2.Provides.Val = append(x2.Provides.Val, "x") | 	x2.Provides = append(x2.Provides, "x") | ||||||
|  |  | ||||||
| 	err = db.InsertPackage(x1) | 	err := database.InsertPackage(ctx, x1) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err = db.InsertPackage(x2) | 	err = database.InsertPackage(ctx, x2) | ||||||
| 	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 | 	pkgs, err := database.GetPkgs(ctx, "name = 'x2'") | ||||||
| 	err = db.DB().Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');") |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if dbPkg.Name != "x2" { | 	if len(pkgs) != 1 || pkgs[0].Name != "x2" { | ||||||
| 		t.Errorf("Expected x2 package, got %s", dbPkg.Name) | 		t.Errorf("Expected x2 package, got %v", pkgs) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Verify the provides field contains 'x' | ||||||
|  | 	found := false | ||||||
|  | 	for _, p := range pkgs[0].Provides { | ||||||
|  | 		if p == "x" { | ||||||
|  | 			found = true | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if !found { | ||||||
|  | 		t.Errorf("Expected provides to contain 'x'") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										52
									
								
								internal/db/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								internal/db/utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package db | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"database/sql/driver" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  |  | ||||||
|  | 	"golang.org/x/exp/slices" | ||||||
|  | 	"modernc.org/sqlite" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // jsonArrayContains is an SQLite function that checks if a JSON array | ||||||
|  | // in the database contains a given value | ||||||
|  | func jsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) { | ||||||
|  | 	value, ok := args[0].(string) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, errors.New("both arguments to json_array_contains must be strings") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	item, ok := args[1].(string) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, errors.New("both arguments to json_array_contains must be strings") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var array []string | ||||||
|  | 	err := json.Unmarshal([]byte(value), &array) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return slices.Contains(array, item), nil | ||||||
|  | } | ||||||
| @@ -1,93 +0,0 @@ | |||||||
| /* |  | ||||||
|  * ALR - Any Linux Repository |  | ||||||
|  * Copyright (C) 2024 Евгений Храмов |  | ||||||
|  * |  | ||||||
|  * This program is free software: you can redistribute it and/or modify |  | ||||||
|  * it under the terms of the GNU General Public License as published by |  | ||||||
|  * the Free Software Foundation, either version 3 of the License, or |  | ||||||
|  * (at your option) any later version. |  | ||||||
|  * |  | ||||||
|  * This program is distributed in the hope that it will be useful, |  | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|  * GNU General Public License for more details. |  | ||||||
|  * |  | ||||||
|  * You should have received a copy of the GNU General Public License |  | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package dlcache |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"crypto/sha1" |  | ||||||
| 	"encoding/hex" |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
|  |  | ||||||
| 	"plemya-x.ru/alr/internal/config" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // BasePath returns the base path of the download cache |  | ||||||
| func BasePath(ctx context.Context) string { |  | ||||||
| 	return filepath.Join(config.GetPaths(ctx).CacheDir, "dl") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // New creates a new directory with the given ID in the cache. |  | ||||||
| // If a directory with the same ID already exists, |  | ||||||
| // it will be deleted before creating a new one. |  | ||||||
| func New(ctx context.Context, id string) (string, error) { |  | ||||||
| 	h, err := hashID(id) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	itemPath := filepath.Join(BasePath(ctx), h) |  | ||||||
|  |  | ||||||
| 	fi, err := os.Stat(itemPath) |  | ||||||
| 	if err == nil || (fi != nil && !fi.IsDir()) { |  | ||||||
| 		err = os.RemoveAll(itemPath) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return "", err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	err = os.MkdirAll(itemPath, 0o755) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return itemPath, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Get checks if an entry with the given ID |  | ||||||
| // already exists in the cache, and if so, |  | ||||||
| // returns the directory and true. If it |  | ||||||
| // does not exist, it returns an empty string |  | ||||||
| // and false. |  | ||||||
| func Get(ctx context.Context, id string) (string, bool) { |  | ||||||
| 	h, err := hashID(id) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", false |  | ||||||
| 	} |  | ||||||
| 	itemPath := filepath.Join(BasePath(ctx), h) |  | ||||||
|  |  | ||||||
| 	_, err = os.Stat(itemPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", false |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return itemPath, true |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // hashID hashes the input ID with SHA1 |  | ||||||
| // and returns the hex string of the hashed |  | ||||||
| // ID. |  | ||||||
| func hashID(id string) (string, error) { |  | ||||||
| 	h := sha1.New() |  | ||||||
| 	_, err := io.WriteString(h, id) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	return hex.EncodeToString(h.Sum(nil)), nil |  | ||||||
| } |  | ||||||
| @@ -1,76 +0,0 @@ | |||||||
| /* |  | ||||||
|  * ALR - Any Linux Repository |  | ||||||
|  * Copyright (C) 2024 Евгений Храмов |  | ||||||
|  * |  | ||||||
|  * This program is free software: you can redistribute it and/or modify |  | ||||||
|  * it under the terms of the GNU General Public License as published by |  | ||||||
|  * the Free Software Foundation, either version 3 of the License, or |  | ||||||
|  * (at your option) any later version. |  | ||||||
|  * |  | ||||||
|  * This program is distributed in the hope that it will be useful, |  | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|  * GNU General Public License for more details. |  | ||||||
|  * |  | ||||||
|  * You should have received a copy of the GNU General Public License |  | ||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package dlcache_test |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"crypto/sha1" |  | ||||||
| 	"encoding/hex" |  | ||||||
| 	"io" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"testing" |  | ||||||
|  |  | ||||||
| 	"plemya-x.ru/alr/internal/config" |  | ||||||
| 	"plemya-x.ru/alr/internal/dlcache" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func init() { |  | ||||||
| 	dir, err := os.MkdirTemp("/tmp", "alr-dlcache-test.*") |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 	config.GetPaths(context.Background()).RepoDir = dir |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestNew(t *testing.T) { |  | ||||||
| 	const id = "https://example.com" |  | ||||||
| 	dir, err := dlcache.New(id) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("Expected no error, got %s", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	exp := filepath.Join(dlcache.BasePath(), sha1sum(id)) |  | ||||||
| 	if dir != exp { |  | ||||||
| 		t.Errorf("Expected %s, got %s", exp, dir) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fi, err := os.Stat(dir) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("stat: expected no error, got %s", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if !fi.IsDir() { |  | ||||||
| 		t.Errorf("Expected cache item to be a directory") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	dir2, ok := dlcache.Get(id) |  | ||||||
| 	if !ok { |  | ||||||
| 		t.Errorf("Expected Get() to return valid value") |  | ||||||
| 	} |  | ||||||
| 	if dir2 != dir { |  | ||||||
| 		t.Errorf("Expected %s from Get(), got %s", dir, dir2) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func sha1sum(id string) string { |  | ||||||
| 	h := sha1.New() |  | ||||||
| 	_, _ = io.WriteString(h, id) |  | ||||||
| 	return hex.EncodeToString(h.Sum(nil)) |  | ||||||
| } |  | ||||||
							
								
								
									
										663
									
								
								internal/gen/aur.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										663
									
								
								internal/gen/aur.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,663 @@ | |||||||
|  | // 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) | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								internal/gen/funcs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								internal/gen/funcs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||||
|  | // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||||
|  | // | ||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package gen | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  | 	"text/template" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Определяем переменную funcs типа template.FuncMap, которая будет использоваться для | ||||||
|  | // предоставления пользовательских функций в шаблонах | ||||||
|  | var funcs = template.FuncMap{ | ||||||
|  | 	// Функция "tolower" использует strings.ToLower | ||||||
|  | 	// для преобразования строки в нижний регистр | ||||||
|  | 	"tolower": strings.ToLower, | ||||||
|  |  | ||||||
|  | 	// Функция "firstchar" — это лямбда-функция, которая берет строку | ||||||
|  | 	// и возвращает её первый символ | ||||||
|  | 	"firstchar": func(s string) string { | ||||||
|  | 		return s[:1] | ||||||
|  | 	}, | ||||||
|  | } | ||||||
							
								
								
									
										118
									
								
								internal/gen/pip.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								internal/gen/pip.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | |||||||
|  | // 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"       // Пакет для встраивания содержимого файлов в бинарники Go, использовав откладку //go:embed | ||||||
|  | 	"encoding/json" // Пакет для работы с JSON: декодирование и кодирование | ||||||
|  | 	"errors"        // Пакет для создания и обработки ошибок | ||||||
|  | 	"fmt"           // Пакет для форматированного ввода и вывода | ||||||
|  | 	"io"            // Пакет для интерфейсов ввода и вывода | ||||||
|  | 	"net/http"      // Пакет для HTTP-клиентов и серверов | ||||||
|  | 	"text/template" // Пакет для обработки текстовых шаблонов | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Используем директиву //go:embed для встраивания содержимого файла шаблона в строку pipTmpl | ||||||
|  | // Встраивание файла tmpls/pip.tmpl.sh | ||||||
|  | // | ||||||
|  | //go:embed tmpls/pip.tmpl.sh | ||||||
|  | var pipTmpl string | ||||||
|  |  | ||||||
|  | // PipOptions содержит параметры, которые будут переданы в шаблон | ||||||
|  | type PipOptions struct { | ||||||
|  | 	Name        string // Имя пакета | ||||||
|  | 	Version     string // Версия пакета | ||||||
|  | 	Description string // Описание пакета | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // pypiAPIResponse представляет структуру ответа от API PyPI | ||||||
|  | type pypiAPIResponse struct { | ||||||
|  | 	Info pypiInfo  `json:"info"` // Информация о пакете | ||||||
|  | 	URLs []pypiURL `json:"urls"` // Список URL-адресов для загрузки пакета | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Метод SourceURL ищет и возвращает URL исходного distribution для пакета, если он существует | ||||||
|  | func (res pypiAPIResponse) SourceURL() (pypiURL, error) { | ||||||
|  | 	for _, url := range res.URLs { | ||||||
|  | 		if url.PackageType == "sdist" { | ||||||
|  | 			return url, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return pypiURL{}, errors.New("package doesn't have a source distribution") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // pypiInfo содержит основную информацию о пакете, такую как имя, версия и пр. | ||||||
|  | type pypiInfo struct { | ||||||
|  | 	Name     string `json:"name"` | ||||||
|  | 	Version  string `json:"version"` | ||||||
|  | 	Summary  string `json:"summary"` | ||||||
|  | 	Homepage string `json:"home_page"` | ||||||
|  | 	License  string `json:"license"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // pypiURL представляет информацию об одном из доступных для загрузки URL | ||||||
|  | type pypiURL struct { | ||||||
|  | 	Digests     map[string]string `json:"digests"`     // Контрольные суммы для файлов | ||||||
|  | 	Filename    string            `json:"filename"`    // Имя файла | ||||||
|  | 	PackageType string            `json:"packagetype"` // Тип пакета (например sdist) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция Pip загружает информацию о пакете из PyPI и использует шаблон для вывода информации | ||||||
|  | func Pip(w io.Writer, opts PipOptions) error { | ||||||
|  | 	// Создаем новый шаблон с добавлением функций из FuncMap | ||||||
|  | 	tmpl, err := template.New("pip"). | ||||||
|  | 		Funcs(funcs). | ||||||
|  | 		Parse(pipTmpl) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Формируем URL для запроса к PyPI на основании имени и версии пакета | ||||||
|  | 	url := fmt.Sprintf( | ||||||
|  | 		"https://pypi.org/pypi/%s/%s/json", | ||||||
|  | 		opts.Name, | ||||||
|  | 		opts.Version, | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	// Выполняем HTTP GET запрос к PyPI | ||||||
|  | 	res, err := http.Get(url) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer res.Body.Close() // Закрываем тело ответа после завершения работы | ||||||
|  | 	if res.StatusCode != 200 { | ||||||
|  | 		return fmt.Errorf("pypi: %s", res.Status) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Раскодируем ответ JSON от PyPI в структуру pypiAPIResponse | ||||||
|  | 	var resp pypiAPIResponse | ||||||
|  | 	err = json.NewDecoder(res.Body).Decode(&resp) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Если в opts указано описание, используем его вместо описания из PyPI | ||||||
|  | 	if opts.Description != "" { | ||||||
|  | 		resp.Info.Summary = opts.Description | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Выполняем шаблон с использованием данных из resp и записываем результат в w | ||||||
|  | 	return tmpl.Execute(w, resp) | ||||||
|  | } | ||||||
							
								
								
									
										133
									
								
								internal/gen/tmpls/aur.tmpl.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								internal/gen/tmpls/aur.tmpl.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | |||||||
|  | # 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}} | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								internal/gen/tmpls/pip.tmpl.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								internal/gen/tmpls/pip.tmpl.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | # 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/>. | ||||||
|  |  | ||||||
|  | name='python3-{{.Info.Name | tolower}}' | ||||||
|  | version='{{.Info.Version}}' | ||||||
|  | release='1' | ||||||
|  | desc='{{.Info.Summary}}' | ||||||
|  | homepage='{{.Info.Homepage}}' | ||||||
|  | maintainer='Example <user@example.com>' | ||||||
|  | architectures=('all') | ||||||
|  | license=('{{if .Info.License | ne ""}}{{.Info.License}}{{else}}custom:Unknown{{end}}') | ||||||
|  | provides=('{{.Info.Name | tolower}}') | ||||||
|  | conflicts=('{{.Info.Name | tolower}}') | ||||||
|  |  | ||||||
|  | deps=("python3") | ||||||
|  | deps_arch=("python") | ||||||
|  | deps_alpine=("python3") | ||||||
|  |  | ||||||
|  | build_deps=("python3" "python3-pip") | ||||||
|  | build_deps_arch=("python" "python-pip") | ||||||
|  | build_deps_alpine=("python3" "py3-pip") | ||||||
|  |  | ||||||
|  | sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}") | ||||||
|  | checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}') | ||||||
|  |  | ||||||
|  | build() { | ||||||
|  | 	cd "$srcdir/{{.Info.Name}}-${version}" | ||||||
|  |   python -m build --wheel --no-isolation | ||||||
|  | } | ||||||
|  |  | ||||||
|  | package() { | ||||||
|  | 	cd "$srcdir/{{.Info.Name}}-${version}" | ||||||
|  | 	pip install --root="${pkgdir}/" . --no-deps --ignore-installed --disable-pip-version-check | ||||||
|  | } | ||||||
|  |  | ||||||
|  | files() { | ||||||
|  |   printf '"%s" ' ./usr/local/lib/python3.*/site-packages/{{.Info.Name | tolower}}/* | ||||||
|  |   printf '"%s" ' ./usr/local/lib/python3.*/site-packages/{{.Info.Name | tolower}}-${version}.dist-info/* | ||||||
|  | } | ||||||
							
								
								
									
										154
									
								
								internal/logger/hclog.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								internal/logger/hclog.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | |||||||
|  | // 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 logger | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"io" | ||||||
|  | 	"log" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	chLog "github.com/charmbracelet/log" | ||||||
|  | 	"github.com/hashicorp/go-hclog" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type HCLoggerAdapter struct { | ||||||
|  | 	logger *Logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func hclogLevelTochLog(level hclog.Level) chLog.Level { | ||||||
|  | 	switch level { | ||||||
|  | 	case hclog.Debug: | ||||||
|  | 		return chLog.DebugLevel | ||||||
|  | 	case hclog.Info: | ||||||
|  | 		return chLog.InfoLevel | ||||||
|  | 	case hclog.Warn: | ||||||
|  | 		return chLog.WarnLevel | ||||||
|  | 	case hclog.Error: | ||||||
|  | 		return chLog.ErrorLevel | ||||||
|  | 	} | ||||||
|  | 	return chLog.FatalLevel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{}) { | ||||||
|  | 	filteredArgs := make([]interface{}, 0, len(args)) | ||||||
|  | 	for i := 0; i < len(args); i += 2 { | ||||||
|  | 		if i+1 >= len(args) { | ||||||
|  | 			filteredArgs = append(filteredArgs, args[i]) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		key, ok := args[i].(string) | ||||||
|  | 		if !ok || key != "timestamp" { | ||||||
|  | 			filteredArgs = append(filteredArgs, args[i], args[i+1]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Start ugly hacks | ||||||
|  | 	// Ignore exit messages | ||||||
|  | 	// - https://github.com/hashicorp/go-plugin/issues/331 | ||||||
|  | 	// - https://github.com/hashicorp/go-plugin/issues/203 | ||||||
|  | 	// - https://github.com/hashicorp/go-plugin/issues/192 | ||||||
|  | 	var chLogLevel chLog.Level | ||||||
|  | 	if msg == "plugin process exited" || | ||||||
|  | 		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") { | ||||||
|  | 		chLogLevel = chLog.DebugLevel | ||||||
|  | 	} else { | ||||||
|  | 		chLogLevel = hclogLevelTochLog(level) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	a.logger.l.Log(chLogLevel, msg, filteredArgs...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) Trace(msg string, args ...interface{}) { | ||||||
|  | 	a.Log(hclog.Trace, msg, args...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) Debug(msg string, args ...interface{}) { | ||||||
|  | 	a.Log(hclog.Debug, msg, args...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) Info(msg string, args ...interface{}) { | ||||||
|  | 	a.Log(hclog.Info, msg, args...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) Warn(msg string, args ...interface{}) { | ||||||
|  | 	a.Log(hclog.Warn, msg, args...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) Error(msg string, args ...interface{}) { | ||||||
|  | 	a.Log(hclog.Error, msg, args...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) IsTrace() bool { | ||||||
|  | 	return a.logger.l.GetLevel() <= chLog.DebugLevel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) IsDebug() bool { | ||||||
|  | 	return a.logger.l.GetLevel() <= chLog.DebugLevel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) IsInfo() bool { | ||||||
|  | 	return a.logger.l.GetLevel() <= chLog.InfoLevel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) IsWarn() bool { | ||||||
|  | 	return a.logger.l.GetLevel() <= chLog.WarnLevel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) IsError() bool { | ||||||
|  | 	return a.logger.l.GetLevel() <= chLog.ErrorLevel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) ImpliedArgs() []interface{} { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) With(args ...interface{}) hclog.Logger { | ||||||
|  | 	return a | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) Name() string { | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) Named(name string) hclog.Logger { | ||||||
|  | 	return a | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) ResetNamed(name string) hclog.Logger { | ||||||
|  | 	return a | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) SetLevel(level hclog.Level) { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *HCLoggerAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetHCLoggerAdapter() *HCLoggerAdapter { | ||||||
|  | 	return &HCLoggerAdapter{ | ||||||
|  | 		logger: logger, | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										111
									
								
								internal/logger/log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								internal/logger/log.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | |||||||
|  | // 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 logger | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  |  | ||||||
|  | 	"github.com/charmbracelet/lipgloss" | ||||||
|  |  | ||||||
|  | 	chLog "github.com/charmbracelet/log" | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Logger struct { | ||||||
|  | 	l *chLog.Logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func setupLogger() *chLog.Logger { | ||||||
|  | 	styles := chLog.DefaultStyles() | ||||||
|  | 	logger := chLog.New(os.Stderr) | ||||||
|  | 	styles.Levels[chLog.InfoLevel] = lipgloss.NewStyle(). | ||||||
|  | 		SetString("-->"). | ||||||
|  | 		Foreground(lipgloss.Color("35")) | ||||||
|  | 	styles.Levels[chLog.ErrorLevel] = lipgloss.NewStyle(). | ||||||
|  | 		SetString(gotext.Get("ERROR")). | ||||||
|  | 		Padding(0, 1, 0, 1). | ||||||
|  | 		Background(lipgloss.Color("204")). | ||||||
|  | 		Foreground(lipgloss.Color("0")) | ||||||
|  | 	logger.SetStyles(styles) | ||||||
|  | 	return logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func New() *Logger { | ||||||
|  | 	return &Logger{ | ||||||
|  | 		l: setupLogger(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func slogLevelToLog(level slog.Level) chLog.Level { | ||||||
|  | 	switch level { | ||||||
|  | 	case slog.LevelDebug: | ||||||
|  | 		return chLog.DebugLevel | ||||||
|  | 	case slog.LevelInfo: | ||||||
|  | 		return chLog.InfoLevel | ||||||
|  | 	case slog.LevelWarn: | ||||||
|  | 		return chLog.WarnLevel | ||||||
|  | 	case slog.LevelError: | ||||||
|  | 		return chLog.ErrorLevel | ||||||
|  | 	} | ||||||
|  | 	return chLog.FatalLevel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *Logger) SetLevel(level slog.Level) { | ||||||
|  | 	l.l.SetLevel(slogLevelToLog(level)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool { | ||||||
|  | 	return l.l.Enabled(ctx, level) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *Logger) Handle(ctx context.Context, rec slog.Record) error { | ||||||
|  | 	return l.l.Handle(ctx, rec) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler { | ||||||
|  | 	sl := *l | ||||||
|  | 	sl.l = l.l.WithAttrs(attrs).(*chLog.Logger) | ||||||
|  | 	return &sl | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *Logger) WithGroup(name string) slog.Handler { | ||||||
|  | 	sl := *l | ||||||
|  | 	sl.l = l.l.WithGroup(name).(*chLog.Logger) | ||||||
|  | 	return &sl | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var logger *Logger | ||||||
|  |  | ||||||
|  | func SetupDefault() *Logger { | ||||||
|  | 	logger = New() | ||||||
|  | 	slogLogger := slog.New(logger) | ||||||
|  | 	slog.SetDefault(slogLogger) | ||||||
|  | 	return logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func SetupForGoPlugin() { | ||||||
|  | 	logger.l.SetFormatter(chLog.JSONFormatter) | ||||||
|  | 	chLog.TimestampKey = "@timestamp" | ||||||
|  | 	chLog.MessageKey = "@message" | ||||||
|  | 	chLog.LevelKey = "@level" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetLogger() *Logger { | ||||||
|  | 	return logger | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user