Compare commits
195 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
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: [ main ]
|
||||
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
|
120
.gitea/workflows/release.yaml
Normal file
120
.gitea/workflows/release.yaml
Normal file
@ -0,0 +1,120 @@
|
||||
# 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 && apt-get install -y libcap2-bin bindfs
|
||||
|
||||
- name: Build alr
|
||||
env:
|
||||
IGNORE_ROOT_CHECK: 1
|
||||
run: |
|
||||
make build
|
||||
|
||||
- name: Create tar.gz
|
||||
run: |
|
||||
mkdir -p ./out/completion
|
||||
cp alr ./out
|
||||
cp scripts/completion/bash ./out/completion/alr
|
||||
cp scripts/completion/zsh ./out/completion/_alr
|
||||
|
||||
( cd out && tar -czvf ../alr-${{ env.VERSION }}-linux-x86_64.tar.gz * )
|
||||
|
||||
- name: Release
|
||||
uses: akkuman/gitea-release-action@v1
|
||||
with:
|
||||
body: ${{ steps.changes.outputs.changes }}
|
||||
files: |-
|
||||
alr-${{ env.VERSION }}-linux-x86_64.tar.gz
|
||||
|
||||
- name: Checkout alr-default repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Plemya-x/alr-default
|
||||
token: ${{ secrets.GITEAPUBLIC }}
|
||||
path: alr-default
|
||||
|
||||
- name: Update version in alr-bin
|
||||
run: |
|
||||
# Замените значения в файле с конфигурацией
|
||||
sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh
|
||||
sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh
|
||||
|
||||
- name: Install alr
|
||||
run: |
|
||||
make install
|
||||
|
||||
# temporary fix
|
||||
groupadd wheel
|
||||
usermod -aG wheel root
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
SCRIPT_PATH=alr-default/alr-bin/alr.sh
|
||||
ALR_DISTRO=altlinux ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
||||
ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
||||
ALR_PKG_FORMAT=deb alr build -s "$SCRIPT_PATH"
|
||||
ALR_PKG_FORMAT=archlinux alr build -s "$SCRIPT_PATH"
|
||||
|
||||
- name: Upload assets
|
||||
uses: akkuman/gitea-release-action@v1
|
||||
with:
|
||||
body: ${{ steps.changes.outputs.changes }}
|
||||
files: |-
|
||||
alr-bin+alr-default_${{ env.VERSION }}-1.red80_amd64.deb \
|
||||
alr-bin+alr-default-${{ env.VERSION }}-1-x86_64.pkg.tar.zst \
|
||||
alr-bin+alr-default-${{ env.VERSION }}-1.red80.x86_64.rpm \
|
||||
alr-bin+alr-default-${{ env.VERSION }}-alt1.x86_64.rpm
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
cd alr-default
|
||||
git config user.name "gitea"
|
||||
git config user.email "admin@plemya-x.ru"
|
||||
git add .
|
||||
git commit -m "Обновление версии до ${{ env.VERSION }}"
|
||||
git push
|
69
.gitea/workflows/update-alr-git.yaml
Normal file
69
.gitea/workflows/update-alr-git.yaml
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/>.
|
||||
|
||||
name: Update alr-git
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
changelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: Setup alr-spec
|
||||
run: |
|
||||
uv tool install alr-spec==0.0.5
|
||||
|
||||
- name: Install alr
|
||||
run: |
|
||||
apt-get update && apt-get install -y libcap2-bin
|
||||
curl -fsS https://gitea.plemya-x.ru/Plemya-x/ALR/raw/branch/master/scripts/install.sh | bash
|
||||
|
||||
- name: Checkout this repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set ALR version
|
||||
run: |
|
||||
echo "NEW_ALR_VERSION=$(alr helper git-version)" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout alr-default repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Plemya-x/alr-default
|
||||
token: ${{ secrets.GITEAPUBLIC }}
|
||||
path: alr-default
|
||||
|
||||
- name: Update version
|
||||
working-directory: ./alr-default/alr-git
|
||||
run: |
|
||||
alr-spec set-field version $NEW_ALR_VERSION
|
||||
alr-spec set-field release 1
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
cd alr-default
|
||||
git config user.name "gitea"
|
||||
git config user.email "admin@plemya-x.ru"
|
||||
git add .
|
||||
git commit -m "Обновление версии до $NEW_ALR_VERSION"
|
||||
git push
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -6,3 +6,8 @@
|
||||
.fleet
|
||||
.idea
|
||||
.gigaide
|
||||
|
||||
*.out
|
||||
|
||||
e2e-tests/alr
|
||||
commit_msg.txt
|
50
.golangci.yml
Normal file
50
.golangci.yml
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/>.
|
||||
|
||||
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
|
||||
# - forbidigo
|
||||
|
||||
issues:
|
||||
fix: true
|
||||
exclude-rules:
|
||||
- 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
|
42
.pre-commit-config.yaml
Normal file
42
.pre-commit-config.yaml
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/>.
|
||||
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: test-coverage
|
||||
name: Run test coverage
|
||||
entry: make test-coverage
|
||||
language: system
|
||||
pass_filenames: false
|
||||
|
||||
- id: fmt
|
||||
name: Format code
|
||||
entry: make fmt
|
||||
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: make i18n
|
||||
language: system
|
||||
pass_filenames: false
|
@ -1,9 +0,0 @@
|
||||
platform: linux/amd64
|
||||
pipeline:
|
||||
release:
|
||||
image: goreleaser/goreleaser
|
||||
commands:
|
||||
- goreleaser release
|
||||
secrets: [ gitea_token, aur_key ]
|
||||
when:
|
||||
event: tag
|
47
Makefile
47
Makefile
@ -1,6 +1,6 @@
|
||||
NAME := alr
|
||||
GIT_VERSION = $(shell git describe --tags )
|
||||
|
||||
IGNORE_ROOT_CHECK ?= 0
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
BIN := ./$(NAME)
|
||||
@ -11,17 +11,22 @@ ZSH_COMPLETION := $(COMPLETIONS_DIR)/zsh
|
||||
INSTALLED_BASH_COMPLETION := $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(NAME)
|
||||
INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME)
|
||||
|
||||
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
|
||||
|
||||
build: check-no-root $(BIN)
|
||||
|
||||
export CGO_ENABLED := 0
|
||||
$(BIN):
|
||||
go build -ldflags="-X 'gitea.plemya-x.ru/xpamych/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
|
||||
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
|
||||
|
||||
check-no-root:
|
||||
@if [[ "$$(whoami)" == 'root' ]]; then \
|
||||
@if [ "$$IGNORE_ROOT_CHECK" != "1" ] && [ "`whoami`" = "root" ]; then \
|
||||
echo "This target shouldn't run as root" 1>&2; \
|
||||
echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
@ -33,6 +38,13 @@ install: \
|
||||
|
||||
$(INSTALED_BIN): $(BIN)
|
||||
install -Dm755 $< $@
|
||||
setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN)
|
||||
@if id alr >/dev/null 2>&1; then \
|
||||
echo "User 'alr' already exists. Skipping."; \
|
||||
else \
|
||||
useradd -r -s /usr/sbin/nologin alr; \
|
||||
fi
|
||||
install -d -o alr -g alr -m 755 /var/cache/alr /etc/alr
|
||||
|
||||
$(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION)
|
||||
install -Dm755 $< $@
|
||||
@ -48,3 +60,32 @@ uninstall:
|
||||
|
||||
clean clear:
|
||||
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 ./... -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 ./...
|
34
README.md
34
README.md
@ -3,11 +3,13 @@
|
||||
</p>
|
||||
<b></b>
|
||||
|
||||
[](https://goreportcard.com/report/gitea.plemya-x.ru/Plemya-x/ALR)  
|
||||
|
||||
# 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 в вашей системе. Чтобы использовать его, просто выполните следующую команду:
|
||||
|
||||
```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 будет установлен, клонируйте это репозиторий и запустите:
|
||||
|
||||
```shell
|
||||
make build
|
||||
make build -B
|
||||
sudo make install
|
||||
```
|
||||
|
||||
@ -42,20 +44,31 @@ ALR был создан потому, что упаковка программн
|
||||
|
||||
## Документация
|
||||
|
||||
Документация по всем этим вопросам находится в [Wiki](https://gitea.plemya-x.ru/xpamych/ALR/wiki/Home).
|
||||
Документация находится в [Wiki](https://disc.plemya-x.ru/c/alr/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 [Plemya-x/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
|
||||
```
|
||||
Репозиторий пакетов [Plemya-x/alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так:
|
||||
```
|
||||
alr repo add alr-repo https://gitea.plemya-x.ru/Plemya-x/alr-repo.git
|
||||
```
|
||||
Репозиторий Linux-Gaming [Plemya-x/alr-LG](https://gitea.plemya-x.ru/Plemya-x/alr-LG.git) можно подключить так:
|
||||
```
|
||||
alr repo add alr-LG https://gitea.plemya-x.ru/Plemya-x/alr-LG.git
|
||||
```
|
||||
|
||||
---
|
||||
## Соцсети
|
||||
VK - https://vk.com/plemya_kh
|
||||
|
||||
Discord - https://discord.com/channels/817759634105827358/1261631565084233749
|
||||
|
||||
Telegram - https://t.me/plemyakh
|
||||
|
||||
## Спасибы
|
||||
@ -68,3 +81,6 @@ Telegram - https://t.me/plemyakh
|
||||
- <https://github.com/goreleaser/nfpm>
|
||||
- <https://github.com/charmbracelet/bubbletea>
|
||||
- <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">19.0%</text>
|
||||
<text x="86" y="14">19.0%</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 |
310
build.go
310
build.go
@ -1,100 +1,238 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/urfave/cli/v2"
|
||||
"plemya-x.ru/alr/internal/config"
|
||||
"plemya-x.ru/alr/internal/osutils"
|
||||
"plemya-x.ru/alr/internal/types"
|
||||
"plemya-x.ru/alr/pkg/build"
|
||||
"plemya-x.ru/alr/pkg/loggerctx"
|
||||
"plemya-x.ru/alr/pkg/manager"
|
||||
"plemya-x.ru/alr/pkg/repos"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||
)
|
||||
|
||||
var buildCmd = &cli.Command{
|
||||
Name: "build",
|
||||
Usage: "Build a local package",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "script",
|
||||
Aliases: []string{"s"},
|
||||
Value: "alr.sh",
|
||||
Usage: "Path to the build script",
|
||||
func BuildCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "build",
|
||||
Usage: gotext.Get("Build a local package"),
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "script",
|
||||
Aliases: []string{"s"},
|
||||
Value: "alr.sh",
|
||||
Usage: gotext.Get("Path to the build script"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "subpackage",
|
||||
Aliases: []string{"sb"},
|
||||
Usage: gotext.Get("Specify subpackage in script (for multi package script only)"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "package",
|
||||
Aliases: []string{"p"},
|
||||
Usage: gotext.Get("Name of the package to build and its repo (example: default/go-bin)"),
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: gotext.Get("Build package from scratch even if there's an already built package available"),
|
||||
},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "package",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Name of the package to build and its repo (example: default/go-bin)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Build package from scratch even if there's an already built package available",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := c.Context
|
||||
log := loggerctx.From(ctx)
|
||||
|
||||
script := c.String("script")
|
||||
if c.String("package") != "" {
|
||||
script = filepath.Join(config.GetPaths(ctx).RepoDir, c.String("package"), "alr.sh")
|
||||
}
|
||||
|
||||
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||
}
|
||||
|
||||
pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{
|
||||
Script: script,
|
||||
Manager: mgr,
|
||||
Clean: c.Bool("clean"),
|
||||
Interactive: c.Bool("interactive"),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("Error building package").Err(err).Send()
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal("Error getting working directory").Err(err).Send()
|
||||
}
|
||||
|
||||
for _, pkgPath := range pkgPaths {
|
||||
name := filepath.Base(pkgPath)
|
||||
err = osutils.Move(pkgPath, filepath.Join(wd, name))
|
||||
if err != nil {
|
||||
log.Fatal("Error moving the package").Err(err).Send()
|
||||
Action: func(c *cli.Context) error {
|
||||
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
|
||||
}
|
||||
|
||||
wd, wdCleanup, err := Mount(wd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wdCleanup()
|
||||
|
||||
ctx := c.Context
|
||||
|
||||
deps, err := appbuilder.
|
||||
New(ctx).
|
||||
WithConfig().
|
||||
WithDB().
|
||||
WithReposNoPull().
|
||||
WithDistroInfo().
|
||||
WithManager().
|
||||
Build()
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer deps.Defer()
|
||||
|
||||
var script string
|
||||
var packages []string
|
||||
|
||||
var res []*build.BuiltDep
|
||||
|
||||
var scriptArgs *build.BuildPackageFromScriptArgs
|
||||
var dbArgs *build.BuildPackageFromDbArgs
|
||||
|
||||
buildArgs := &build.BuildArgs{
|
||||
Opts: &types.BuildOpts{
|
||||
Clean: c.Bool("clean"),
|
||||
Interactive: c.Bool("interactive"),
|
||||
},
|
||||
PkgFormat_: build.GetPkgFormat(deps.Manager),
|
||||
Info: deps.Info,
|
||||
}
|
||||
|
||||
switch {
|
||||
case c.IsSet("script"):
|
||||
script, err = filepath.Abs(c.String("script"))
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Cannot get absolute script path"), err)
|
||||
}
|
||||
|
||||
subpackage := c.String("subpackage")
|
||||
|
||||
if subpackage != "" {
|
||||
packages = append(packages, subpackage)
|
||||
}
|
||||
|
||||
scriptArgs = &build.BuildPackageFromScriptArgs{
|
||||
Script: script,
|
||||
Packages: packages,
|
||||
BuildArgs: *buildArgs,
|
||||
}
|
||||
case c.IsSet("package"):
|
||||
// TODO: handle multiple packages
|
||||
packageInput := c.String("package")
|
||||
|
||||
arr := strings.Split(packageInput, "/")
|
||||
var packageSearch string
|
||||
if len(arr) == 2 {
|
||||
packageSearch = arr[1]
|
||||
} else {
|
||||
packageSearch = arr[0]
|
||||
}
|
||||
|
||||
pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageSearch})
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
if scriptArgs != nil {
|
||||
scriptFile := filepath.Base(scriptArgs.Script)
|
||||
newScriptDir, scriptDirCleanup, err := Mount(filepath.Dir(scriptArgs.Script))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer scriptDirCleanup()
|
||||
scriptArgs.Script = filepath.Join(newScriptDir, scriptFile)
|
||||
}
|
||||
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
installer, installerClose, err := build.GetSafeInstaller()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer installerClose()
|
||||
|
||||
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
72
e2e-tests/addrepo_test.go
Normal file
72
e2e-tests/addrepo_test.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/>.
|
||||
|
||||
//go:build e2e
|
||||
|
||||
package e2etests_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestE2EAlrAddRepo(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"add-repo-remove-repo",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
err := r.Exec(e2e.NewCommand(
|
||||
"sudo",
|
||||
"alr",
|
||||
"addrepo",
|
||||
"--name",
|
||||
"alr-repo",
|
||||
"--url",
|
||||
"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git",
|
||||
))
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = r.Exec(e2e.NewCommand(
|
||||
"bash",
|
||||
"-c",
|
||||
"cat /etc/alr/alr.toml",
|
||||
))
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = r.Exec(e2e.NewCommand(
|
||||
"sudo",
|
||||
"alr",
|
||||
"removerepo",
|
||||
"--name",
|
||||
"alr-repo",
|
||||
))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = r.Exec(e2e.NewCommand(
|
||||
"bash",
|
||||
"-c",
|
||||
"cat /etc/alr/alr.toml",
|
||||
), e2e.WithExecOptionStdout(&buf))
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, buf.String(), "rootCmd")
|
||||
},
|
||||
)
|
||||
}
|
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EBashCompletion(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"bash-completion",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
execShouldNoError(t, r, "alr", "install", "--generate-bash-completion")
|
||||
},
|
||||
)
|
||||
}
|
202
e2e-tests/common_test.go
Normal file
202
e2e-tests/common_test.go
Normal file
@ -0,0 +1,202 @@
|
||||
// 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 (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
expect "github.com/tailscale/goexpect"
|
||||
)
|
||||
|
||||
// DebugWriter оборачивает io.Writer и логирует все записываемые данные.
|
||||
type DebugWriter struct {
|
||||
prefix string
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func (d *DebugWriter) Write(p []byte) (n int, err error) {
|
||||
log.Printf("%s: Writing data: %q", d.prefix, p) // Логируем данные
|
||||
return d.writer.Write(p)
|
||||
}
|
||||
|
||||
// DebugReader оборачивает io.Reader и логирует все читаемые данные.
|
||||
type DebugReader struct {
|
||||
prefix string
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func (d *DebugReader) Read(p []byte) (n int, err error) {
|
||||
n, err = d.reader.Read(p)
|
||||
if n > 0 {
|
||||
log.Printf("%s: Read data: %q", d.prefix, p[:n]) // Логируем данные
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func e2eSpawn(runnable e2e.Runnable, command e2e.Command, timeout time.Duration, opts ...expect.Option) (expect.Expecter, <-chan error, error, *io.PipeWriter) {
|
||||
resCh := make(chan error)
|
||||
|
||||
// Создаем pipe для stdin и stdout
|
||||
stdinReader, stdinWriter := io.Pipe()
|
||||
stdoutReader, stdoutWriter := io.Pipe()
|
||||
|
||||
debugStdinReader := &DebugReader{prefix: "STDIN", reader: stdinReader}
|
||||
debugStdoutWriter := &DebugWriter{prefix: "STDOUT", writer: stdoutWriter}
|
||||
|
||||
go func() {
|
||||
err := runnable.Exec(
|
||||
command,
|
||||
e2e.WithExecOptionStdout(debugStdoutWriter),
|
||||
e2e.WithExecOptionStdin(debugStdinReader),
|
||||
e2e.WithExecOptionStderr(debugStdoutWriter),
|
||||
)
|
||||
|
||||
resCh <- err
|
||||
}()
|
||||
|
||||
exp, chnErr, err := expect.SpawnGeneric(&expect.GenOptions{
|
||||
In: stdinWriter,
|
||||
Out: stdoutReader,
|
||||
Wait: func() error {
|
||||
return <-resCh
|
||||
},
|
||||
Close: func() error {
|
||||
stdinWriter.Close()
|
||||
stdoutReader.Close()
|
||||
return nil
|
||||
},
|
||||
Check: func() bool { return true },
|
||||
}, timeout, expect.Verbose(true), expect.VerboseWriter(os.Stdout))
|
||||
|
||||
return exp, chnErr, err, stdinWriter
|
||||
}
|
||||
|
||||
var ALL_SYSTEMS []string = []string{
|
||||
"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 dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testing.T, runnable e2e.Runnable)) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
for _, id := range ids {
|
||||
t.Run(id, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dockerName := fmt.Sprintf("alr-test-%s-%s", name, id)
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(dockerName))
|
||||
hashSum := hash.Sum(nil)
|
||||
hashString := hex.EncodeToString(hashSum)
|
||||
truncatedHash := hashString[:8]
|
||||
e, err := e2e.New(e2e.WithVerbose(), e2e.WithName(fmt.Sprintf("alr-%s", truncatedHash)))
|
||||
assert.NoError(t, err)
|
||||
t.Cleanup(e.Close)
|
||||
imageId := fmt.Sprintf("ghcr.io/maks1ms/alr-e2e-test-image-%s", id)
|
||||
runnable := e.Runnable(dockerName).Init(
|
||||
e2e.StartOptions{
|
||||
Image: imageId,
|
||||
Volumes: []string{
|
||||
"./alr:/tmp/alr",
|
||||
},
|
||||
Privileged: true,
|
||||
},
|
||||
)
|
||||
assert.NoError(t, e2e.StartAndWaitReady(runnable))
|
||||
err = runnable.Exec(e2e.NewCommand("/bin/alr-test-setup", "alr-install"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = runnable.Exec(e2e.NewCommand("/bin/alr-test-setup", "passwordless-sudo-setup"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f(t, runnable)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func execShouldNoError(t *testing.T, r e2e.Runnable, cmd string, args ...string) {
|
||||
assert.NoError(t, r.Exec(e2e.NewCommand(cmd, args...)))
|
||||
}
|
||||
|
||||
func execShouldError(t *testing.T, r e2e.Runnable, cmd string, args ...string) {
|
||||
assert.Error(t, r.Exec(e2e.NewCommand(cmd, args...)))
|
||||
}
|
||||
|
||||
func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expects []expect.Batcher) {
|
||||
exp, _, err, _ := e2eSpawn(
|
||||
r,
|
||||
e2e.NewCommand("/bin/bash"), 25*time.Second,
|
||||
expect.Verbose(true),
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
_, err = exp.ExpectBatch(
|
||||
expects,
|
||||
timeout,
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
const REPO_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 e2e.Runnable) {
|
||||
execShouldNoError(t, r,
|
||||
"sudo",
|
||||
"alr",
|
||||
"repo",
|
||||
"add",
|
||||
REPO_NAME_FOR_E2E_TESTS,
|
||||
REPO_URL_FOR_E2E_TESTS,
|
||||
)
|
||||
|
||||
execShouldNoError(t, r,
|
||||
"sudo",
|
||||
"alr",
|
||||
"ref",
|
||||
)
|
||||
}
|
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EFirejailedPackage(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"firejailed-package",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
defaultPrepare(t, r)
|
||||
execShouldNoError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg", REPO_NAME_FOR_E2E_TESTS))
|
||||
execShouldError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg-incorrect", REPO_NAME_FOR_E2E_TESTS))
|
||||
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh'")
|
||||
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh.profile'")
|
||||
},
|
||||
)
|
||||
}
|
43
e2e-tests/fix_test.go
Normal file
43
e2e-tests/fix_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"
|
||||
"time"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
expect "github.com/tailscale/goexpect"
|
||||
)
|
||||
|
||||
func TestE2EAlrFix(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"run-fix",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
runTestCommands(t, r, time.Second*30, []expect.Batcher{
|
||||
&expect.BSnd{S: "alr fix\n"},
|
||||
&expect.BExp{R: `--> Done`},
|
||||
&expect.BSnd{S: "echo $?\n"},
|
||||
&expect.BExp{R: `^0\n$`},
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EGroupAndSummaryField(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"group-and-summary-field",
|
||||
RPM_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
defaultPrepare(t, r)
|
||||
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group.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_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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EIssue32Interactive(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-32-interactive",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EIssue41AutoreqSkiplist(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-41-autoreq-skiplist",
|
||||
AUTOREQ_AUTOPROV_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EIssue50InstallMultiple(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-50-install-multiple",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EIssue53LcAllCInfo(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-53-lc-all-c-info",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EIssue59RmCompletion(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-59-rm-completion",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
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$")
|
||||
},
|
||||
)
|
||||
}
|
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EIssue72InstallWithDeps(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-72-install-with-deps",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EIssue74Upgradable(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-74-upgradable",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EIssue75InstallWithDeps(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-75-ref-specify",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
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")
|
||||
},
|
||||
)
|
||||
}
|
53
e2e-tests/issue_76_single_package_repo_test.go
Normal file
53
e2e-tests/issue_76_single_package_repo_test.go
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/>.
|
||||
|
||||
//go:build e2e
|
||||
|
||||
package e2etests_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func Test75SinglePackageRepo(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-76-single-package-repo",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
execShouldNoError(t, r,
|
||||
"sudo",
|
||||
"alr",
|
||||
"repo",
|
||||
"add",
|
||||
REPO_NAME_FOR_E2E_TESTS,
|
||||
"https://gitea.plemya-x.ru/Maks1mS/test-single-package-alr-repo.git",
|
||||
)
|
||||
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "1075c918be")
|
||||
execShouldNoError(t, r, "alr", "ref")
|
||||
execShouldNoError(t, r, "sudo", "alr", "in", "test-single-repo")
|
||||
execShouldNoError(t, r, "sh", "-c", "alr list -U")
|
||||
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
|
||||
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "5e361c50d7")
|
||||
execShouldNoError(t, r, "sudo", "alr", "ref")
|
||||
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1")
|
||||
execShouldNoError(t, r, "sudo", "alr", "up")
|
||||
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
|
||||
},
|
||||
)
|
||||
}
|
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EIssue81MultiplePackages(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-81-multiple-packages",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
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"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
)
|
||||
|
||||
func TestE2EIssue91MultiplePackages(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-91-set-repo-ref",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
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")
|
||||
},
|
||||
)
|
||||
}
|
49
e2e-tests/issue_94_twice_build_test.go
Normal file
49
e2e-tests/issue_94_twice_build_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 (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestE2EIssue94TwiceBuild(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"issue-94-twice-build",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
defaultPrepare(t, r)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
err := r.Exec(
|
||||
e2e.NewCommand("sudo", "alr", "in", "test-94-app"),
|
||||
e2e.WithExecOptionStderr(&stderr),
|
||||
)
|
||||
assert.NoError(t, err, "command failed")
|
||||
|
||||
output := stderr.String()
|
||||
assert.Equal(t, 1, strings.Count(output, "Building package name=test-94-dep"))
|
||||
},
|
||||
)
|
||||
}
|
44
e2e-tests/version_test.go
Normal file
44
e2e-tests/version_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
// 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"
|
||||
"time"
|
||||
|
||||
"github.com/efficientgo/e2e"
|
||||
|
||||
expect "github.com/tailscale/goexpect"
|
||||
)
|
||||
|
||||
func TestE2EAlrVersion(t *testing.T) {
|
||||
dockerMultipleRun(
|
||||
t,
|
||||
"check-version",
|
||||
COMMON_SYSTEMS,
|
||||
func(t *testing.T, r e2e.Runnable) {
|
||||
runTestCommands(t, r, time.Second*10, []expect.Batcher{
|
||||
&expect.BSnd{S: "alr version\n"},
|
||||
&expect.BExp{R: `^v\d+\.\d+\.\d+(?:-\d+-g[a-f0-9]+)?\n$`},
|
||||
&expect.BSnd{S: "echo $?\n"},
|
||||
&expect.BExp{R: `^0\n$`},
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
157
fix.go
157
fix.go
@ -1,64 +1,129 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/urfave/cli/v2"
|
||||
"plemya-x.ru/alr/internal/config"
|
||||
"plemya-x.ru/alr/internal/db"
|
||||
"plemya-x.ru/alr/pkg/loggerctx"
|
||||
"plemya-x.ru/alr/pkg/repos"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
var fixCmd = &cli.Command{
|
||||
Name: "fix",
|
||||
Usage: "Attempt to fix problems with ALR",
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := c.Context
|
||||
log := loggerctx.From(ctx)
|
||||
func FixCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "fix",
|
||||
Usage: gotext.Get("Attempt to fix problems with ALR"),
|
||||
Action: func(c *cli.Context) error {
|
||||
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.Close()
|
||||
paths := config.GetPaths(ctx)
|
||||
ctx := c.Context
|
||||
|
||||
log.Info("Removing cache directory").Send()
|
||||
deps, err := appbuilder.
|
||||
New(ctx).
|
||||
WithConfig().
|
||||
Build()
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer deps.Defer()
|
||||
|
||||
err := os.RemoveAll(paths.CacheDir)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to remove cache directory").Err(err).Send()
|
||||
}
|
||||
cfg := deps.Cfg
|
||||
|
||||
log.Info("Rebuilding cache").Send()
|
||||
paths := cfg.GetPaths()
|
||||
|
||||
err = os.MkdirAll(paths.CacheDir, 0o755)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create new cache directory").Err(err).Send()
|
||||
}
|
||||
slog.Info(gotext.Get("Clearing cache directory"))
|
||||
|
||||
err = repos.Pull(ctx, config.Config(ctx).Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repos").Err(err).Send()
|
||||
}
|
||||
dir, err := os.Open(paths.CacheDir)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err)
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
log.Info("Done").Send()
|
||||
entries, err := dir.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
for _, entry := range entries {
|
||||
fullPath := filepath.Join(paths.CacheDir, entry)
|
||||
|
||||
if err := makeWritableRecursive(fullPath); err != nil {
|
||||
slog.Debug("Failed to make path writable", "path", fullPath, "error", err)
|
||||
}
|
||||
|
||||
err = os.RemoveAll(fullPath)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err)
|
||||
}
|
||||
}
|
||||
|
||||
slog.Info(gotext.Get("Rebuilding cache"))
|
||||
|
||||
err = os.MkdirAll(paths.CacheDir, 0o755)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err)
|
||||
}
|
||||
|
||||
deps, err = appbuilder.
|
||||
New(ctx).
|
||||
WithConfig().
|
||||
WithDB().
|
||||
WithReposForcePull().
|
||||
Build()
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer deps.Defer()
|
||||
|
||||
slog.Info(gotext.Get("Done"))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeWritableRecursive(path string) error {
|
||||
return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
93
gen.go
93
gen.go
@ -1,45 +1,66 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/urfave/cli/v2"
|
||||
"plemya-x.ru/alr/pkg/gen"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/gen"
|
||||
)
|
||||
|
||||
var genCmd = &cli.Command{
|
||||
Name: "generate",
|
||||
Usage: "Generate a ALR script from a template",
|
||||
Aliases: []string{"gen"},
|
||||
Subcommands: []*cli.Command{
|
||||
genPipCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var genPipCmd = &cli.Command{
|
||||
Name: "pip",
|
||||
Usage: "Generate a ALR script for a pip module",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "version",
|
||||
Aliases: []string{"v"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "description",
|
||||
Aliases: []string{"d"},
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return gen.Pip(os.Stdout, gen.PipOptions{
|
||||
Name: c.String("name"),
|
||||
Version: c.String("version"),
|
||||
Description: c.String("description"),
|
||||
})
|
||||
},
|
||||
func GenCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "generate",
|
||||
Usage: gotext.Get("Generate a ALR script from a template"),
|
||||
Aliases: []string{"gen"},
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "pip",
|
||||
Usage: gotext.Get("Generate a ALR script for a pip module"),
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "version",
|
||||
Aliases: []string{"v"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "description",
|
||||
Aliases: []string{"d"},
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return gen.Pip(os.Stdout, gen.PipOptions{
|
||||
Name: c.String("name"),
|
||||
Version: c.String("version"),
|
||||
Description: c.String("description"),
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
97
go.mod
97
go.mod
@ -1,39 +1,47 @@
|
||||
module plemya-x.ru/alr
|
||||
module gitea.plemya-x.ru/Plemya-x/ALR
|
||||
|
||||
go 1.21
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.21.3
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/PuerkitoBio/purell v1.2.0
|
||||
github.com/alecthomas/chroma/v2 v2.9.1
|
||||
github.com/charmbracelet/bubbles v0.16.1
|
||||
github.com/charmbracelet/bubbletea v0.24.2
|
||||
github.com/charmbracelet/lipgloss v0.8.0
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
github.com/caarlos0/env v3.5.0+incompatible
|
||||
github.com/charmbracelet/bubbles v0.20.0
|
||||
github.com/charmbracelet/bubbletea v1.2.4
|
||||
github.com/charmbracelet/lipgloss v1.0.0
|
||||
github.com/charmbracelet/log v0.4.0
|
||||
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0
|
||||
github.com/go-git/go-billy/v5 v5.6.0
|
||||
github.com/go-git/go-git/v5 v5.13.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/jmoiron/sqlx v1.3.5
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
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/mitchellh/mapstructure v1.5.0
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/pelletier/go-toml/v2 v2.1.0
|
||||
github.com/schollz/progressbar/v3 v3.13.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5
|
||||
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090
|
||||
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6
|
||||
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/text v0.15.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/text v0.23.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.25.0
|
||||
mvdan.cc/sh/v3 v3.7.0
|
||||
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283
|
||||
mvdan.cc/sh/v3 v3.10.0
|
||||
xorm.io/xorm v1.3.9
|
||||
)
|
||||
|
||||
require (
|
||||
@ -43,7 +51,7 @@ require (
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
||||
@ -51,67 +59,85 @@ require (
|
||||
github.com/bodgit/sevenzip v1.3.0 // indirect
|
||||
github.com/bodgit/windows v1.0.0 // indirect
|
||||
github.com/cavaliergopher/cpio v1.0.1 // 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.3.8 // 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/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/creack/pty v1.1.24 // 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/dsnet/compress v0.0.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/efficientgo/core v1.0.0-rc.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/goccy/go-json v0.8.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect
|
||||
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/gookit/color v1.5.1 // indirect
|
||||
github.com/goreleaser/chglog v0.6.1 // indirect
|
||||
github.com/goreleaser/fileglob v1.3.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-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/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // 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/termenv v0.15.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/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/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
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
|
||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
|
||||
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/term v0.20.0 // indirect
|
||||
golang.org/x/tools v0.16.0 // indirect
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/term v0.30.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||
google.golang.org/grpc v1.58.3 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
@ -122,4 +148,5 @@ require (
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect
|
||||
)
|
||||
|
280
go.sum
280
go.sum
@ -17,6 +17,10 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8=
|
||||
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||
@ -37,8 +41,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/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/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
|
||||
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/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
|
||||
@ -59,6 +63,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
|
||||
@ -67,36 +73,46 @@ 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/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
|
||||
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/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
||||
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
|
||||
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
|
||||
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
|
||||
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
|
||||
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
|
||||
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/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
||||
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
|
||||
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
||||
github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
|
||||
github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
|
||||
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
|
||||
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
|
||||
github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
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/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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
|
||||
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
||||
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
|
||||
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
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.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -107,30 +123,46 @@ 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/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/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/efficientgo/core v1.0.0-rc.0 h1:jJoA0N+C4/knWYVZ6GrdHOtDyrg8Y/TR4vFpTaqTsqs=
|
||||
github.com/efficientgo/core v1.0.0-rc.0/go.mod h1:kQa0V74HNYMfuJH6jiPiwNdpWXl4xd/K4tzlrcvYDQI=
|
||||
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 h1:C/FNIs+MtAJgQYLJ9FX/ACFYyDRuLYoXTmueErrOJyA=
|
||||
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0/go.mod h1:plsKU0YHE9uX+7utvr7SiDtVBSHJyEfHRO4UnUgDmts=
|
||||
github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug=
|
||||
github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
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.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
|
||||
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.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
||||
github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E=
|
||||
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/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-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
|
||||
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -147,6 +179,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.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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@ -155,8 +191,12 @@ 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.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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
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=
|
||||
@ -166,13 +206,13 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S3
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE=
|
||||
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
|
||||
github.com/google/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.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.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/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/goreleaser/chglog v0.6.1 h1:NZKiX8l0FTQPRzBgKST7knvNZmZ04f7PEGkN2wInfhE=
|
||||
@ -183,14 +223,22 @@ github.com/goreleaser/nfpm/v2 v2.41.0 h1:JyMzS/EwqaWbFs+7Z9oZ4Hkk4or00gUTqwm9Dgr
|
||||
github.com/goreleaser/nfpm/v2 v2.41.0/go.mod h1:VPc5kF5OgfA+BosV/A2aB+Vg34honjWvp0Vt8ogsSi0=
|
||||
github.com/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/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/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.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/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
@ -199,13 +247,20 @@ 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/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/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc=
|
||||
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/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
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/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
@ -224,33 +279,38 @@ 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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
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.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.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
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.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-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/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/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/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.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
@ -259,18 +319,31 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
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/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
@ -281,31 +354,38 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.36.0 h1:78hJTing+BLYLjhXE+Z2BubeEymH5Lr0/Mt8FKkxxYo=
|
||||
github.com/prometheus/common v0.36.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
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.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
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/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
|
||||
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
|
||||
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
|
||||
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||
@ -322,11 +402,14 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
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.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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns=
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
@ -342,17 +425,11 @@ 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/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||
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/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
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/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
|
||||
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090 h1:RVC8XvWo6Yw4HUshqx4TSzuBDScDghafU6QFRJ4xPZg=
|
||||
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM=
|
||||
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6 h1:4xCBxLPBn3Y2DuIcj8zQ1tQOFLrpu6tEIGUWn/Q6zPM=
|
||||
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6/go.mod h1:NmfCFqwq7X/aqa/ZVkIysj17JyMEY4Bb5E921kMswNo=
|
||||
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.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@ -368,10 +445,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-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.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
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-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -380,8 +455,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-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-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
|
||||
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
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-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@ -400,11 +475,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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@ -421,15 +496,15 @@ 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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -437,10 +512,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-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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -450,6 +525,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-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-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-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -457,27 +533,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-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-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-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-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-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.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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
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.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -486,10 +557,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.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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -517,9 +586,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-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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
|
||||
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -536,6 +604,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@ -549,6 +619,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-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@ -556,16 +628,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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
|
||||
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/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 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
@ -603,10 +687,12 @@ modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
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/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
||||
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
|
||||
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
|
||||
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283 h1:BXCLPeA8g2M6qYngicyxyB/2Bo4J54Q9Rb+8jMmE3ik=
|
||||
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283/go.mod h1:itzL9Jx52VXOhRaucFHuMpa3y7iwjnuLGdNvypoh/S4=
|
||||
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
|
||||
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM=
|
||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
|
||||
xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=
|
||||
|
156
helper.go
156
helper.go
@ -1,86 +1,110 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"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/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{
|
||||
Name: "helper",
|
||||
Usage: "Run a ALR helper command",
|
||||
ArgsUsage: `<helper_name|"list">`,
|
||||
Subcommands: []*cli.Command{helperListCmd},
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "dest-dir",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "The directory that the install commands will install to",
|
||||
Value: "dest",
|
||||
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
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := c.Context
|
||||
log := loggerctx.From(ctx)
|
||||
}
|
||||
|
||||
if c.Args().Len() < 1 {
|
||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
||||
}
|
||||
return &cli.Command{
|
||||
Name: "helper",
|
||||
Usage: gotext.Get("Run a ALR helper command"),
|
||||
ArgsUsage: `<helper_name|"list">`,
|
||||
Subcommands: []*cli.Command{helperListCmd},
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "dest-dir",
|
||||
Aliases: []string{"d"},
|
||||
Usage: gotext.Get("The directory that the install commands will install to"),
|
||||
Value: "dest",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := c.Context
|
||||
|
||||
helper, ok := helpers.Helpers[c.Args().First()]
|
||||
if !ok {
|
||||
log.Fatal("No such helper command").Str("name", c.Args().First()).Send()
|
||||
}
|
||||
if c.Args().Len() < 1 {
|
||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal("Error getting working directory").Err(err).Send()
|
||||
}
|
||||
helper, ok := helpers.Helpers[c.Args().First()]
|
||||
if !ok {
|
||||
slog.Error(gotext.Get("No such helper command"), "name", c.Args().First())
|
||||
return cli.Exit(gotext.Get("No such helper command"), 1)
|
||||
}
|
||||
|
||||
info, err := distro.ParseOSRelease(ctx)
|
||||
if err != nil {
|
||||
log.Fatal("Error getting working directory").Err(err).Send()
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
|
||||
}
|
||||
|
||||
hc := interp.HandlerContext{
|
||||
Env: expand.ListEnviron(
|
||||
"pkgdir="+c.String("dest-dir"),
|
||||
"DISTRO_ID="+info.ID,
|
||||
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
|
||||
"ARCH="+cpu.Arch(),
|
||||
),
|
||||
Dir: wd,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
info, err := distro.ParseOSRelease(ctx)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err)
|
||||
}
|
||||
|
||||
return helper(hc, c.Args().First(), c.Args().Slice()[1:])
|
||||
},
|
||||
CustomHelpTemplate: cli.CommandHelpTemplate,
|
||||
BashComplete: func(ctx *cli.Context) {
|
||||
for name := range helpers.Helpers {
|
||||
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
|
||||
},
|
||||
hc := interp.HandlerContext{
|
||||
Env: expand.ListEnviron(
|
||||
"pkgdir="+c.String("dest-dir"),
|
||||
"DISTRO_ID="+info.ID,
|
||||
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
|
||||
"ARCH="+cpu.Arch(),
|
||||
),
|
||||
Dir: wd,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
|
||||
return helper(hc, c.Args().First(), c.Args().Slice()[1:])
|
||||
},
|
||||
CustomHelpTemplate: cli.CommandHelpTemplate,
|
||||
BashComplete: func(ctx *cli.Context) {
|
||||
for name := range helpers.Helpers {
|
||||
fmt.Println(name)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
219
info.go
219
info.go
@ -1,20 +1,21 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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
|
||||
|
||||
@ -22,85 +23,137 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jeandeaual/go-locale"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/urfave/cli/v2"
|
||||
"plemya-x.ru/alr/internal/cliutils"
|
||||
"plemya-x.ru/alr/internal/config"
|
||||
"plemya-x.ru/alr/internal/overrides"
|
||||
"plemya-x.ru/alr/pkg/distro"
|
||||
"plemya-x.ru/alr/pkg/loggerctx"
|
||||
"plemya-x.ru/alr/pkg/repos"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"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/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"
|
||||
)
|
||||
|
||||
var infoCmd = &cli.Command{
|
||||
Name: "info",
|
||||
Usage: "Print information about a package",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Show all information, not just for the current distro",
|
||||
func InfoCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "info",
|
||||
Usage: gotext.Get("Print information about a package"),
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"a"},
|
||||
Usage: gotext.Get("Show all information, not just for the current distro"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := c.Context
|
||||
log := loggerctx.From(ctx)
|
||||
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send()
|
||||
}
|
||||
|
||||
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
||||
found, _, err := repos.FindPkgs(ctx, args.Slice())
|
||||
if err != nil {
|
||||
log.Fatal("Error finding packages").Err(err).Send()
|
||||
}
|
||||
|
||||
if len(found) == 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive"))
|
||||
|
||||
var names []string
|
||||
all := c.Bool("all")
|
||||
|
||||
if !all {
|
||||
info, err := distro.ParseOSRelease(ctx)
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing os-release file").Err(err).Send()
|
||||
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := c.Context
|
||||
deps, err := appbuilder.
|
||||
New(ctx).
|
||||
WithConfig().
|
||||
WithDB().
|
||||
Build()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer deps.Defer()
|
||||
|
||||
result, err := deps.DB.GetPkgs(c.Context, "true")
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
||||
}
|
||||
|
||||
for _, pkg := range result {
|
||||
fmt.Println(pkg.Name)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
Action: func(c *cli.Context) error {
|
||||
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
return cli.Exit(gotext.Get("Command info expected at least 1 argument, got %d", args.Len()), 1)
|
||||
}
|
||||
|
||||
ctx := c.Context
|
||||
|
||||
deps, err := appbuilder.
|
||||
New(ctx).
|
||||
WithConfig().
|
||||
WithDB().
|
||||
WithDistroInfo().
|
||||
WithRepos().
|
||||
Build()
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer deps.Defer()
|
||||
|
||||
rs := deps.Repos
|
||||
|
||||
found, _, err := rs.FindPkgs(ctx, args.Slice())
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error finding packages"), err)
|
||||
}
|
||||
|
||||
if len(found) == 0 {
|
||||
return cliutils.FormatCliExit(gotext.Get("Package not found"), err)
|
||||
}
|
||||
|
||||
pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive"))
|
||||
|
||||
var names []string
|
||||
all := c.Bool("all")
|
||||
|
||||
systemLang, err := locale.GetLanguage()
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Can't detect system language"), err)
|
||||
}
|
||||
if systemLang == "" {
|
||||
systemLang = "en"
|
||||
}
|
||||
names, err = overrides.Resolve(
|
||||
info,
|
||||
overrides.DefaultOpts.
|
||||
WithLanguages([]string{config.SystemLang()}),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal("Error resolving overrides").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
if !all {
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
|
||||
info, err := distro.ParseOSRelease(ctx)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err)
|
||||
}
|
||||
} else {
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
||||
names, err = overrides.Resolve(
|
||||
info,
|
||||
overrides.DefaultOpts.
|
||||
WithLanguages([]string{systemLang}),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||
return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("---")
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
if !all {
|
||||
alrsh.ResolvePackage(&pkg, names)
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
|
||||
}
|
||||
} else {
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
304
install.go
304
install.go
@ -1,122 +1,220 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/urfave/cli/v2"
|
||||
"plemya-x.ru/alr/internal/cliutils"
|
||||
"plemya-x.ru/alr/internal/config"
|
||||
"plemya-x.ru/alr/internal/db"
|
||||
"plemya-x.ru/alr/internal/types"
|
||||
"plemya-x.ru/alr/pkg/build"
|
||||
"plemya-x.ru/alr/pkg/loggerctx"
|
||||
"plemya-x.ru/alr/pkg/manager"
|
||||
"plemya-x.ru/alr/pkg/repos"
|
||||
|
||||
"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/manager"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||
)
|
||||
|
||||
var installCmd = &cli.Command{
|
||||
Name: "install",
|
||||
Usage: "Install a new package",
|
||||
Aliases: []string{"in"},
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Build package from scratch even if there's an already built package available",
|
||||
func InstallCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "install",
|
||||
Usage: gotext.Get("Install a new package"),
|
||||
Aliases: []string{"in"},
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: gotext.Get("Build package from scratch even if there's an already built package available"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := c.Context
|
||||
log := loggerctx.From(ctx)
|
||||
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send()
|
||||
}
|
||||
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||
}
|
||||
|
||||
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
||||
found, notFound, err := repos.FindPkgs(ctx, args.Slice())
|
||||
if err != nil {
|
||||
log.Fatal("Error finding packages").Err(err).Send()
|
||||
}
|
||||
|
||||
pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive"))
|
||||
build.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
|
||||
Manager: mgr,
|
||||
Clean: c.Bool("clean"),
|
||||
Interactive: c.Bool("interactive"),
|
||||
})
|
||||
return nil
|
||||
},
|
||||
BashComplete: func(c *cli.Context) {
|
||||
log := loggerctx.From(c.Context)
|
||||
result, err := db.GetPkgs(c.Context, "true")
|
||||
if err != nil {
|
||||
log.Fatal("Error getting packages").Err(err).Send()
|
||||
}
|
||||
defer result.Close()
|
||||
|
||||
for result.Next() {
|
||||
var pkg db.Package
|
||||
err = result.StructScan(&pkg)
|
||||
if err != nil {
|
||||
log.Fatal("Error iterating over packages").Err(err).Send()
|
||||
Action: utils.RootNeededAction(func(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil)
|
||||
}
|
||||
|
||||
fmt.Println(pkg.Name)
|
||||
}
|
||||
},
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
installer, installerClose, err := build.GetSafeInstaller()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer installerClose()
|
||||
|
||||
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scripter, scripterClose, err := build.GetSafeScriptExecutor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer scripterClose()
|
||||
|
||||
ctx := c.Context
|
||||
|
||||
deps, err := appbuilder.
|
||||
New(ctx).
|
||||
WithConfig().
|
||||
WithDB().
|
||||
WithRepos().
|
||||
WithDistroInfo().
|
||||
WithManager().
|
||||
Build()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer deps.Defer()
|
||||
|
||||
builder, err := build.NewMainBuilder(
|
||||
deps.Cfg,
|
||||
deps.Manager,
|
||||
deps.Repos,
|
||||
scripter,
|
||||
installer,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = builder.InstallPkgs(
|
||||
ctx,
|
||||
&build.BuildArgs{
|
||||
Opts: &types.BuildOpts{
|
||||
Clean: c.Bool("clean"),
|
||||
Interactive: c.Bool("interactive"),
|
||||
},
|
||||
Info: deps.Info,
|
||||
PkgFormat_: build.GetPkgFormat(deps.Manager),
|
||||
},
|
||||
args.Slice(),
|
||||
)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error when installing the package"), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := c.Context
|
||||
deps, err := appbuilder.
|
||||
New(ctx).
|
||||
WithConfig().
|
||||
WithDB().
|
||||
Build()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer deps.Defer()
|
||||
|
||||
result, err := deps.DB.GetPkgs(c.Context, "true")
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
||||
}
|
||||
|
||||
for _, pkg := range result {
|
||||
fmt.Println(pkg.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
var removeCmd = &cli.Command{
|
||||
Name: "remove",
|
||||
Usage: "Remove an installed package",
|
||||
Aliases: []string{"rm"},
|
||||
Action: func(c *cli.Context) error {
|
||||
log := loggerctx.From(c.Context)
|
||||
func RemoveCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "remove",
|
||||
Usage: gotext.Get("Remove an installed package"),
|
||||
Aliases: []string{"rm"},
|
||||
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
|
||||
ctx := c.Context
|
||||
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
log.Fatalf("Command remove expected at least 1 argument, got %d", args.Len()).Send()
|
||||
}
|
||||
deps, err := appbuilder.
|
||||
New(ctx).
|
||||
WithConfig().
|
||||
WithDB().
|
||||
WithManager().
|
||||
Build()
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer deps.Defer()
|
||||
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
err := mgr.Remove(nil, c.Args().Slice()...)
|
||||
if err != nil {
|
||||
log.Fatal("Error removing packages").Err(err).Send()
|
||||
}
|
||||
result, err := deps.DB.GetPkgs(c.Context, "true")
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
for _, pkg := range result {
|
||||
_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fmt.Println(pkg.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
Action: utils.RootNeededAction(func(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
return cliutils.FormatCliExit(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len()), nil)
|
||||
}
|
||||
|
||||
deps, err := appbuilder.
|
||||
New(c.Context).
|
||||
WithManager().
|
||||
Build()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer deps.Defer()
|
||||
|
||||
if err := deps.Manager.Remove(&manager.Opts{
|
||||
NoConfirm: !c.Bool("interactive"),
|
||||
}, c.Args().Slice()...); err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error removing packages"), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
280
internal.go
Normal file
280
internal.go
Normal file
@ -0,0 +1,280 @@
|
||||
// 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 (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"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/constants"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||
)
|
||||
|
||||
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())
|
||||
|
||||
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 InternalInstallCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "_internal-installer",
|
||||
HideHelp: true,
|
||||
Hidden: true,
|
||||
Action: func(c *cli.Context) error {
|
||||
logger.SetupForGoPlugin()
|
||||
|
||||
if err := utils.EnsureIsAlrUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Before escalating the rights, we made sure that
|
||||
// this is an ALR user, so it looks safe.
|
||||
err := utils.EscalateToRootUid()
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit("cannot escalate to root", err)
|
||||
}
|
||||
|
||||
deps, err := appbuilder.
|
||||
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.InstallerPlugin{
|
||||
Impl: build.NewInstaller(
|
||||
manager.Detect(),
|
||||
),
|
||||
},
|
||||
},
|
||||
Logger: logger,
|
||||
})
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Mount(target string) (string, func(), error) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(exe, "_internal-temporary-mount", target)
|
||||
|
||||
stdoutPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to get stdout pipe: %w", err)
|
||||
}
|
||||
|
||||
stdinPipe, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to get stdin pipe: %w", err)
|
||||
}
|
||||
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", nil, fmt.Errorf("failed to start mount: %w", err)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(stdoutPipe)
|
||||
var mountPath string
|
||||
if scanner.Scan() {
|
||||
mountPath = scanner.Text()
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
_ = cmd.Process.Kill()
|
||||
return "", nil, fmt.Errorf("failed to read mount output: %w", err)
|
||||
}
|
||||
|
||||
if mountPath == "" {
|
||||
_ = cmd.Process.Kill()
|
||||
return "", nil, errors.New("mount failed: no target path returned")
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
slog.Debug("cleanup triggered")
|
||||
_, _ = fmt.Fprintln(stdinPipe, "")
|
||||
_ = cmd.Wait()
|
||||
}
|
||||
|
||||
return mountPath, cleanup, nil
|
||||
}
|
||||
|
||||
func InternalMountCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "_internal-temporary-mount",
|
||||
HideHelp: true,
|
||||
Hidden: true,
|
||||
Action: func(c *cli.Context) error {
|
||||
logger.SetupForGoPlugin()
|
||||
|
||||
sourceDir := c.Args().First()
|
||||
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit("cannot get current user", err)
|
||||
}
|
||||
|
||||
_, alrGid, err := utils.GetUidGidAlrUser()
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit("cannot get alr user", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(sourceDir); err != nil {
|
||||
return cliutils.FormatCliExit(fmt.Sprintf("cannot read %s", sourceDir), err)
|
||||
}
|
||||
|
||||
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Before escalating the rights, we made sure that
|
||||
// 1. user in wheel group
|
||||
// 2. user can access sourceDir
|
||||
if err := utils.EscalateToRootUid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := syscall.Setgid(alrGid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(constants.AlrRunDir, 0o770); err != nil {
|
||||
return cliutils.FormatCliExit(fmt.Sprintf("failed to create %s", constants.AlrRunDir), err)
|
||||
}
|
||||
|
||||
if err := os.Chown(constants.AlrRunDir, 0, alrGid); err != nil {
|
||||
return cliutils.FormatCliExit(fmt.Sprintf("failed to chown %s", constants.AlrRunDir), err)
|
||||
}
|
||||
|
||||
targetDir := filepath.Join(constants.AlrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid()))
|
||||
// 0750: owner (root) and group (alr)
|
||||
if err := os.MkdirAll(targetDir, 0o750); err != nil {
|
||||
return cliutils.FormatCliExit("error creating bindfs target directory", err)
|
||||
}
|
||||
|
||||
// chown AlrRunDir/mounts/bindfs-* to (root:alr),
|
||||
// so alr user can access dir
|
||||
if err := os.Chown(targetDir, 0, alrGid); err != nil {
|
||||
return cliutils.FormatCliExit("failed to chown bindfs directory", err)
|
||||
}
|
||||
|
||||
bindfsCmd := exec.Command(
|
||||
"bindfs",
|
||||
fmt.Sprintf("--map=%s/alr:@%s/@alr", u.Uid, u.Gid),
|
||||
sourceDir,
|
||||
targetDir,
|
||||
)
|
||||
|
||||
bindfsCmd.Stderr = os.Stderr
|
||||
|
||||
if err := bindfsCmd.Run(); err != nil {
|
||||
return cliutils.FormatCliExit("failed to strart bindfs", err)
|
||||
}
|
||||
|
||||
fmt.Println(targetDir)
|
||||
|
||||
_, _ = bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
|
||||
slog.Debug("start unmount", "dir", targetDir)
|
||||
|
||||
umountCmd := exec.Command("umount", targetDir)
|
||||
umountCmd.Stderr = os.Stderr
|
||||
if err := umountCmd.Run(); err != nil {
|
||||
return cliutils.FormatCliExit(fmt.Sprintf("failed to unmount %s", targetDir), err)
|
||||
}
|
||||
|
||||
if err := os.Remove(targetDir); err != nil {
|
||||
return cliutils.FormatCliExit(fmt.Sprintf("error removing directory %s", targetDir), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
697
internal/build/build.go
Normal file
697
internal/build/build.go
Normal file
@ -0,0 +1,697 @@
|
||||
// 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/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 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 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 InstallerExecutor interface {
|
||||
InstallLocal(paths []string, opts *manager.Opts) error
|
||||
Install(pkgs []string, opts *manager.Opts) error
|
||||
RemoveAlreadyInstalled(pkgs []string) ([]string, 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
|
||||
|
||||
if !input.opts.Clean {
|
||||
var remainingVars []*alrsh.Package
|
||||
for _, vars := range varsOfPackages {
|
||||
builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
builtDeps = append(builtDeps, &BuiltDep{
|
||||
Path: builtPkgPath,
|
||||
})
|
||||
} else {
|
||||
remainingVars = append(remainingVars, vars)
|
||||
}
|
||||
}
|
||||
|
||||
if len(remainingVars) == 0 {
|
||||
return builtDeps, nil
|
||||
}
|
||||
}
|
||||
|
||||
slog.Debug("ViewScript")
|
||||
slog.Debug("", "varsOfPackages", varsOfPackages)
|
||||
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, 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
|
||||
for _, d := range depends {
|
||||
if _, found := depNames[d]; !found {
|
||||
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...))
|
||||
|
||||
return builtDeps, 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(
|
||||
GetBuiltPaths(res),
|
||||
&manager.Opts{
|
||||
NoConfirm: !input.BuildOpts().Interactive,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
|
||||
pkgs := cliutils.FlattenPkgs(
|
||||
ctx,
|
||||
found,
|
||||
"install",
|
||||
input.BuildOpts().Interactive,
|
||||
)
|
||||
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, error) {
|
||||
var builtDeps []*BuiltDep
|
||||
if len(pkgs) > 0 {
|
||||
deps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builtDeps, err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return builtDeps, 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(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(GetBuiltPaths(builtDeps), &manager.Opts{
|
||||
NoConfirm: !input.BuildOpts().Interactive,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(repoDeps) > 0 {
|
||||
err = i.installerExecutor.Install(repoDeps, &manager.Opts{
|
||||
NoConfirm: !input.BuildOpts().Interactive,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
237
internal/build/firejail.go
Normal file
237
internal/build/firejail.go
Normal file
@ -0,0 +1,237 @@
|
||||
// ALR - Any Linux Repository
|
||||
// Copyright (C) 2025 The ALR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/goreleaser/nfpm/v2/files"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
firejailedDir = "/usr/lib/alr/firejailed"
|
||||
defaultDirMode = 0o755
|
||||
defaultScriptMode = 0o755
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidDestination = errors.New("invalid destination path")
|
||||
ErrMissingProfile = errors.New("default profile is missing")
|
||||
ErrEmptyPackageName = errors.New("package name cannot be empty")
|
||||
)
|
||||
|
||||
var binaryDirectories = []string{
|
||||
"/usr/bin/",
|
||||
"/bin/",
|
||||
"/usr/local/bin/",
|
||||
}
|
||||
|
||||
func applyFirejailIntegration(
|
||||
vars *alrsh.Package,
|
||||
dirs types.Directories,
|
||||
contents []*files.Content,
|
||||
) ([]*files.Content, error) {
|
||||
slog.Info(gotext.Get("Applying FireJail integration"), "package", vars.Name)
|
||||
|
||||
if err := createFirejailedDirectory(dirs.PkgDir); err != nil {
|
||||
return nil, fmt.Errorf("failed to create firejailed directory: %w", err)
|
||||
}
|
||||
|
||||
newContents, err := processBinaryFiles(vars, contents, dirs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to process binary files: %w", err)
|
||||
}
|
||||
|
||||
return append(contents, newContents...), nil
|
||||
}
|
||||
|
||||
func createFirejailedDirectory(pkgDir string) error {
|
||||
firejailedPath := filepath.Join(pkgDir, firejailedDir)
|
||||
return os.MkdirAll(firejailedPath, defaultDirMode)
|
||||
}
|
||||
|
||||
func processBinaryFiles(pkg *alrsh.Package, contents []*files.Content, dirs types.Directories) ([]*files.Content, error) {
|
||||
var newContents []*files.Content
|
||||
|
||||
for _, content := range contents {
|
||||
if content.Type == "dir" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isBinaryFile(content.Destination) {
|
||||
slog.Debug("content not binary file", "content", content)
|
||||
continue
|
||||
}
|
||||
|
||||
slog.Debug("process content", "content", content)
|
||||
|
||||
newContent, err := createFirejailedBinary(pkg, content, dirs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create firejailed binary for %s: %w", content.Destination, err)
|
||||
}
|
||||
|
||||
if newContent != nil {
|
||||
newContents = append(newContents, newContent...)
|
||||
}
|
||||
}
|
||||
|
||||
return newContents, nil
|
||||
}
|
||||
|
||||
func isBinaryFile(destination string) bool {
|
||||
for _, binDir := range binaryDirectories {
|
||||
if strings.HasPrefix(destination, binDir) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func createFirejailedBinary(
|
||||
pkg *alrsh.Package,
|
||||
content *files.Content,
|
||||
dirs types.Directories,
|
||||
) ([]*files.Content, error) {
|
||||
origFilePath, err := generateFirejailedPath(content.Destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
profiles := pkg.FireJailProfiles.Resolved()
|
||||
sourceProfilePath, ok := profiles[content.Destination]
|
||||
|
||||
if !ok {
|
||||
sourceProfilePath, ok = profiles["default"]
|
||||
if !ok {
|
||||
return nil, errors.New("default profile is missing")
|
||||
}
|
||||
}
|
||||
|
||||
sourceProfilePath = filepath.Join(dirs.ScriptDir, sourceProfilePath)
|
||||
dest, err := createFirejailProfilePath(content.Destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = createProfile(filepath.Join(dirs.PkgDir, dest), sourceProfilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := osutils.Move(content.Source, filepath.Join(dirs.PkgDir, origFilePath)); err != nil {
|
||||
return nil, fmt.Errorf("failed to move original binary: %w", err)
|
||||
}
|
||||
|
||||
// Create wrapper script
|
||||
if err := createWrapperScript(content.Source, origFilePath, dest); err != nil {
|
||||
return nil, fmt.Errorf("failed to create wrapper script: %w", err)
|
||||
}
|
||||
|
||||
profile, err := getContentFromPath(dest, dirs.PkgDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bin, err := getContentFromPath(origFilePath, dirs.PkgDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []*files.Content{
|
||||
bin,
|
||||
profile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getContentFromPath(path, base string) (*files.Content, error) {
|
||||
absPath := filepath.Join(base, path)
|
||||
|
||||
fi, err := os.Lstat(absPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get file info: %w", err)
|
||||
}
|
||||
|
||||
return &files.Content{
|
||||
Source: absPath,
|
||||
Destination: path,
|
||||
FileInfo: &files.ContentFileInfo{
|
||||
MTime: fi.ModTime(),
|
||||
Mode: fi.Mode(),
|
||||
Size: fi.Size(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateSafeName(destination string) (string, error) {
|
||||
cleanPath := strings.TrimPrefix(destination, ".")
|
||||
if cleanPath == "" {
|
||||
return "", fmt.Errorf("invalid destination path: %s", destination)
|
||||
}
|
||||
return strings.ReplaceAll(cleanPath, "/", "_"), nil
|
||||
}
|
||||
|
||||
func generateFirejailedPath(destination string) (string, error) {
|
||||
safeName, err := generateSafeName(destination)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(firejailedDir, safeName), nil
|
||||
}
|
||||
|
||||
func createProfile(destProfilePath, profilePath string) error {
|
||||
srcFile, err := os.Open(profilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
destFile, err := os.Create(destProfilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
_, err = io.Copy(destFile, srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return destFile.Sync()
|
||||
}
|
||||
|
||||
func createWrapperScript(scriptPath, origFilePath, profilePath string) error {
|
||||
scriptContent := fmt.Sprintf("#!/bin/bash\nexec firejail --profile=%q %q \"$@\"\n", profilePath, origFilePath)
|
||||
return os.WriteFile(scriptPath, []byte(scriptContent), defaultDirMode)
|
||||
}
|
||||
|
||||
func createFirejailProfilePath(binaryPath string) (string, error) {
|
||||
name, err := generateSafeName(binaryPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(firejailedDir, fmt.Sprintf("%s.profile", name)), nil
|
||||
}
|
316
internal/build/firejail_test.go
Normal file
316
internal/build/firejail_test.go
Normal file
@ -0,0 +1,316 @@
|
||||
// ALR - Any Linux Repository
|
||||
// Copyright (C) 2025 The ALR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/goreleaser/nfpm/v2/files"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||
)
|
||||
|
||||
func TestIsBinaryFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
destination string
|
||||
expected bool
|
||||
}{
|
||||
{"usr/bin binary", "/usr/bin/test", true},
|
||||
{"bin binary", "/bin/test", true},
|
||||
{"usr/local/bin binary", "/usr/local/bin/test", true},
|
||||
{"lib file", "/usr/lib/test.so", false},
|
||||
{"etc file", "/etc/config", false},
|
||||
{"empty destination", "", false},
|
||||
{"root level file", "./test", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := isBinaryFile(tt.destination)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateSafeName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
destination string
|
||||
expected string
|
||||
expectError bool
|
||||
}{
|
||||
{"usr/bin path", "./usr/bin/test", "_usr_bin_test", false},
|
||||
{"bin path", "./bin/test", "_bin_test", false},
|
||||
{"nested path", "./usr/local/bin/app", "_usr_local_bin_app", false},
|
||||
{"path with spaces", "./usr/bin/my app", "_usr_bin_my app", false},
|
||||
{"empty after trim", ".", "", true},
|
||||
{"empty string", "", "", true},
|
||||
{"only dots", "..", ".", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := generateSafeName(tt.destination)
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateWrapperScript(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
origFilePath string
|
||||
profilePath string
|
||||
expectedContent string
|
||||
}{
|
||||
{
|
||||
"basic wrapper",
|
||||
"/usr/lib/alr/firejailed/_usr_bin_test",
|
||||
"/usr/lib/alr/firejailed/_usr_bin_test.profile",
|
||||
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_test.profile\" \"/usr/lib/alr/firejailed/_usr_bin_test\" \"$@\"\n",
|
||||
},
|
||||
{
|
||||
"path with spaces",
|
||||
"/usr/lib/alr/firejailed/_usr_bin_my_app",
|
||||
"/usr/lib/alr/firejailed/_usr_bin_my_app.profile",
|
||||
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_my_app.profile\" \"/usr/lib/alr/firejailed/_usr_bin_my_app\" \"$@\"\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
scriptPath := filepath.Join(tmpDir, "wrapper.sh")
|
||||
|
||||
err := createWrapperScript(scriptPath, tt.origFilePath, tt.profilePath)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.FileExists(t, scriptPath)
|
||||
|
||||
content, err := os.ReadFile(scriptPath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedContent, string(content))
|
||||
|
||||
// Check file permissions
|
||||
info, err := os.Stat(scriptPath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, os.FileMode(defaultDirMode), info.Mode())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateFirejailedBinary(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupFunc func(string) (*alrsh.Package, *files.Content, types.Directories)
|
||||
expectError bool
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
"successful creation with default profile",
|
||||
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||
os.MkdirAll(pkgDir, 0o755)
|
||||
os.MkdirAll(scriptDir, 0o755)
|
||||
|
||||
srcBinary := filepath.Join(tmpDir, "test-binary")
|
||||
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||
|
||||
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
||||
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile\nnet none"), 0o644)
|
||||
|
||||
pkg := &alrsh.Package{
|
||||
Name: "test-pkg",
|
||||
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
|
||||
"": {"default": "default.profile"},
|
||||
}),
|
||||
}
|
||||
alrsh.ResolvePackage(pkg, []string{""})
|
||||
|
||||
content := &files.Content{
|
||||
Source: srcBinary,
|
||||
Destination: "./usr/bin/test-binary",
|
||||
Type: "file",
|
||||
}
|
||||
|
||||
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||
return pkg, content, dirs
|
||||
},
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"successful creation with specific profile",
|
||||
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||
os.MkdirAll(pkgDir, 0o755)
|
||||
os.MkdirAll(scriptDir, 0o755)
|
||||
|
||||
srcBinary := filepath.Join(tmpDir, "special-binary")
|
||||
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755)
|
||||
|
||||
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
||||
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
|
||||
|
||||
specialProfile := filepath.Join(scriptDir, "special.profile")
|
||||
os.WriteFile(specialProfile, []byte("include /etc/firejail/default.profile\nnet none\nprivate-tmp"), 0o644)
|
||||
|
||||
pkg := &alrsh.Package{
|
||||
Name: "test-pkg",
|
||||
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
|
||||
"": {"default": "default.profile", "/usr/bin/special-binary": "special.profile"},
|
||||
}),
|
||||
}
|
||||
alrsh.ResolvePackage(pkg, []string{""})
|
||||
|
||||
content := &files.Content{
|
||||
Source: srcBinary,
|
||||
Destination: "./usr/bin/special-binary",
|
||||
Type: "file",
|
||||
}
|
||||
|
||||
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||
return pkg, content, dirs
|
||||
},
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"missing default profile",
|
||||
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||
os.MkdirAll(pkgDir, 0o755)
|
||||
os.MkdirAll(scriptDir, 0o755)
|
||||
|
||||
srcBinary := filepath.Join(tmpDir, "test-binary")
|
||||
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||
|
||||
pkg := &alrsh.Package{
|
||||
Name: "test-pkg",
|
||||
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {}}),
|
||||
}
|
||||
alrsh.ResolvePackage(pkg, []string{""})
|
||||
|
||||
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
|
||||
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||
return pkg, content, dirs
|
||||
},
|
||||
true,
|
||||
"default profile is missing",
|
||||
},
|
||||
{
|
||||
"profile file not found",
|
||||
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||
os.MkdirAll(pkgDir, 0o755)
|
||||
os.MkdirAll(scriptDir, 0o755)
|
||||
|
||||
srcBinary := filepath.Join(tmpDir, "test-binary")
|
||||
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||
|
||||
pkg := &alrsh.Package{
|
||||
Name: "test-pkg",
|
||||
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "nonexistent.profile"}}),
|
||||
}
|
||||
alrsh.ResolvePackage(pkg, []string{""})
|
||||
|
||||
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
|
||||
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||
return pkg, content, dirs
|
||||
},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"invalid destination path",
|
||||
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||
os.MkdirAll(pkgDir, 0o755)
|
||||
os.MkdirAll(scriptDir, 0o755)
|
||||
|
||||
srcBinary := filepath.Join(tmpDir, "test-binary")
|
||||
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||
|
||||
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
||||
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
|
||||
|
||||
pkg := &alrsh.Package{Name: "test-pkg", FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "default.profile"}})}
|
||||
alrsh.ResolvePackage(pkg, []string{""})
|
||||
|
||||
content := &files.Content{Source: srcBinary, Destination: ".", Type: "file"}
|
||||
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||
return pkg, content, dirs
|
||||
},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
pkg, content, dirs := tt.setupFunc(tmpDir)
|
||||
|
||||
err := createFirejailedDirectory(dirs.PkgDir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := createFirejailedBinary(pkg, content, dirs)
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
if tt.errorMsg != "" {
|
||||
assert.Contains(t, err.Error(), tt.errorMsg)
|
||||
}
|
||||
assert.Nil(t, result)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
|
||||
binContent := result[0]
|
||||
assert.Contains(t, binContent.Destination, "usr/lib/alr/firejailed/")
|
||||
assert.FileExists(t, binContent.Source)
|
||||
|
||||
profileContent := result[1]
|
||||
assert.Contains(t, profileContent.Destination, "usr/lib/alr/firejailed/")
|
||||
assert.Contains(t, profileContent.Destination, ".profile")
|
||||
assert.FileExists(t, profileContent.Source)
|
||||
|
||||
assert.FileExists(t, content.Source)
|
||||
wrapperBytes, err := os.ReadFile(content.Source)
|
||||
assert.NoError(t, err)
|
||||
wrapper := string(wrapperBytes)
|
||||
assert.Contains(t, wrapper, "#!/bin/bash")
|
||||
assert.Contains(t, wrapper, "firejail --profile=")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
54
internal/build/installer.go
Normal file
54
internal/build/installer.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/>.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"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(paths []string, opts *manager.Opts) error {
|
||||
return i.mgr.InstallLocal(opts, paths...)
|
||||
}
|
||||
|
||||
func (i *Installer) Install(pkgs []string, opts *manager.Opts) error {
|
||||
return i.mgr.Install(opts, pkgs...)
|
||||
}
|
||||
|
||||
func (i *Installer) RemoveAlreadyInstalled(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
|
||||
}
|
40
internal/build/safe_common.go
Normal file
40
internal/build/safe_common.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/>.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func setCommonCmdEnv(cmd *exec.Cmd) {
|
||||
cmd.Env = []string{
|
||||
"HOME=/var/cache/alr",
|
||||
"LOGNAME=alr",
|
||||
"USER=alr",
|
||||
"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)
|
||||
}
|
||||
}
|
||||
}
|
150
internal/build/safe_installer.go
Normal file
150
internal/build/safe_installer.go
Normal file
@ -0,0 +1,150 @@
|
||||
// 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"
|
||||
"net/rpc"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||
)
|
||||
|
||||
type InstallerPlugin struct {
|
||||
Impl InstallerExecutor
|
||||
}
|
||||
|
||||
type InstallerRPC struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
type InstallerRPCServer struct {
|
||||
Impl InstallerExecutor
|
||||
}
|
||||
|
||||
type InstallArgs struct {
|
||||
PackagesOrPaths []string
|
||||
Opts *manager.Opts
|
||||
}
|
||||
|
||||
func (r *InstallerRPC) InstallLocal(paths []string, opts *manager.Opts) error {
|
||||
return r.client.Call("Plugin.InstallLocal", &InstallArgs{
|
||||
PackagesOrPaths: paths,
|
||||
Opts: opts,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (s *InstallerRPCServer) InstallLocal(args *InstallArgs, reply *struct{}) error {
|
||||
return s.Impl.InstallLocal(args.PackagesOrPaths, args.Opts)
|
||||
}
|
||||
|
||||
func (r *InstallerRPC) Install(pkgs []string, opts *manager.Opts) error {
|
||||
return r.client.Call("Plugin.Install", &InstallArgs{
|
||||
PackagesOrPaths: pkgs,
|
||||
Opts: opts,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (s *InstallerRPCServer) Install(args *InstallArgs, reply *struct{}) error {
|
||||
return s.Impl.Install(args.PackagesOrPaths, args.Opts)
|
||||
}
|
||||
|
||||
func (r *InstallerRPC) RemoveAlreadyInstalled(paths []string) ([]string, error) {
|
||||
var val []string
|
||||
err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, &val)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (s *InstallerRPCServer) RemoveAlreadyInstalled(pkgs []string, res *[]string) error {
|
||||
vars, err := s.Impl.RemoveAlreadyInstalled(pkgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*res = vars
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *InstallerPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
||||
return &InstallerRPC{client: c}, nil
|
||||
}
|
||||
|
||||
func (p *InstallerPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
||||
return &InstallerRPCServer{Impl: p.Impl}, nil
|
||||
}
|
||||
|
||||
func GetSafeInstaller() (InstallerExecutor, func(), error) {
|
||||
var err error
|
||||
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cmd := exec.Command(executable, "_internal-installer")
|
||||
setCommonCmdEnv(cmd)
|
||||
|
||||
slog.Debug("safe installer setup", "uid", syscall.Getuid(), "gid", syscall.Getgid())
|
||||
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: HandshakeConfig,
|
||||
Plugins: pluginMap,
|
||||
Cmd: cmd,
|
||||
Logger: logger.GetHCLoggerAdapter(),
|
||||
SkipHostEnv: true,
|
||||
UnixSocketConfig: &plugin.UnixSocketConfig{
|
||||
Group: "alr",
|
||||
},
|
||||
SyncStderr: os.Stderr,
|
||||
})
|
||||
rpcClient, err := client.Client()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var cleanupOnce sync.Once
|
||||
cleanup := func() {
|
||||
cleanupOnce.Do(func() {
|
||||
client.Kill()
|
||||
})
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
slog.Debug("close installer")
|
||||
cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
raw, err := rpcClient.Dispense("installer")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
executor, ok := raw.(InstallerExecutor)
|
||||
if !ok {
|
||||
err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return executor, cleanup, nil
|
||||
}
|
273
internal/build/safe_script_executor.go
Normal file
273
internal/build/safe_script_executor.go
Normal file
@ -0,0 +1,273 @@
|
||||
// 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"
|
||||
"log/slog"
|
||||
"net/rpc"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
)
|
||||
|
||||
var HandshakeConfig = plugin.HandshakeConfig{
|
||||
ProtocolVersion: 1,
|
||||
MagicCookieKey: "ALR_PLUGIN",
|
||||
MagicCookieValue: "-",
|
||||
}
|
||||
|
||||
type ScriptExecutorPlugin struct {
|
||||
Impl ScriptExecutor
|
||||
}
|
||||
|
||||
type ScriptExecutorRPCServer struct {
|
||||
Impl ScriptExecutor
|
||||
}
|
||||
|
||||
// =============================
|
||||
//
|
||||
// ReadScript
|
||||
//
|
||||
|
||||
func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
|
||||
var resp *alrsh.ScriptFile
|
||||
err := s.client.Call("Plugin.ReadScript", scriptPath, &resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *alrsh.ScriptFile) error {
|
||||
file, err := s.Impl.ReadScript(context.Background(), scriptPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp = *file
|
||||
return nil
|
||||
}
|
||||
|
||||
// =============================
|
||||
//
|
||||
// ExecuteFirstPass
|
||||
//
|
||||
|
||||
type ExecuteFirstPassArgs struct {
|
||||
Input *BuildInput
|
||||
Sf *alrsh.ScriptFile
|
||||
}
|
||||
|
||||
type ExecuteFirstPassResp struct {
|
||||
BasePkg string
|
||||
VarsOfPackages []*alrsh.Package
|
||||
}
|
||||
|
||||
func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
|
||||
var resp *ExecuteFirstPassResp
|
||||
err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{
|
||||
Input: input,
|
||||
Sf: sf,
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return resp.BasePkg, resp.VarsOfPackages, nil
|
||||
}
|
||||
|
||||
func (s *ScriptExecutorRPCServer) ExecuteFirstPass(args *ExecuteFirstPassArgs, resp *ExecuteFirstPassResp) error {
|
||||
basePkg, varsOfPackages, err := s.Impl.ExecuteFirstPass(context.Background(), args.Input, args.Sf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp = ExecuteFirstPassResp{
|
||||
BasePkg: basePkg,
|
||||
VarsOfPackages: varsOfPackages,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// =============================
|
||||
//
|
||||
// PrepareDirs
|
||||
//
|
||||
|
||||
type PrepareDirsArgs struct {
|
||||
Input *BuildInput
|
||||
BasePkg string
|
||||
}
|
||||
|
||||
func (s *ScriptExecutorRPC) PrepareDirs(
|
||||
ctx context.Context,
|
||||
input *BuildInput,
|
||||
basePkg string,
|
||||
) error {
|
||||
err := s.client.Call("Plugin.PrepareDirs", &PrepareDirsArgs{
|
||||
Input: input,
|
||||
BasePkg: basePkg,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ScriptExecutorRPCServer) PrepareDirs(args *PrepareDirsArgs, reply *struct{}) error {
|
||||
err := s.Impl.PrepareDirs(
|
||||
context.Background(),
|
||||
args.Input,
|
||||
args.BasePkg,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// =============================
|
||||
//
|
||||
// ExecuteSecondPass
|
||||
//
|
||||
|
||||
type ExecuteSecondPassArgs struct {
|
||||
Input *BuildInput
|
||||
Sf *alrsh.ScriptFile
|
||||
VarsOfPackages []*alrsh.Package
|
||||
RepoDeps []string
|
||||
BuiltDeps []*BuiltDep
|
||||
BasePkg string
|
||||
}
|
||||
|
||||
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 []*BuiltDep
|
||||
err := s.client.Call("Plugin.ExecuteSecondPass", &ExecuteSecondPassArgs{
|
||||
Input: input,
|
||||
Sf: sf,
|
||||
VarsOfPackages: varsOfPackages,
|
||||
RepoDeps: repoDeps,
|
||||
BuiltDeps: builtDeps,
|
||||
BasePkg: basePkg,
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *[]*BuiltDep) error {
|
||||
res, err := s.Impl.ExecuteSecondPass(
|
||||
context.Background(),
|
||||
args.Input,
|
||||
args.Sf,
|
||||
args.VarsOfPackages,
|
||||
args.RepoDeps,
|
||||
args.BuiltDeps,
|
||||
args.BasePkg,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp = res
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// ============================
|
||||
//
|
||||
|
||||
func (p *ScriptExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
||||
return &ScriptExecutorRPCServer{Impl: p.Impl}, nil
|
||||
}
|
||||
|
||||
func (p *ScriptExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
||||
return &ScriptExecutorRPC{client: c}, nil
|
||||
}
|
||||
|
||||
type ScriptExecutorRPC struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
var pluginMap = map[string]plugin.Plugin{
|
||||
"script-executor": &ScriptExecutorPlugin{},
|
||||
"installer": &InstallerPlugin{},
|
||||
}
|
||||
|
||||
func GetSafeScriptExecutor() (ScriptExecutor, func(), error) {
|
||||
var err error
|
||||
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(executable, "_internal-safe-script-executor")
|
||||
setCommonCmdEnv(cmd)
|
||||
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: HandshakeConfig,
|
||||
Plugins: pluginMap,
|
||||
Cmd: cmd,
|
||||
Logger: logger.GetHCLoggerAdapter(),
|
||||
SkipHostEnv: true,
|
||||
UnixSocketConfig: &plugin.UnixSocketConfig{
|
||||
Group: "alr",
|
||||
},
|
||||
SyncStderr: os.Stderr,
|
||||
})
|
||||
rpcClient, err := client.Client()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var cleanupOnce sync.Once
|
||||
cleanup := func() {
|
||||
cleanupOnce.Do(func() {
|
||||
client.Kill()
|
||||
})
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
slog.Debug("close script-executor")
|
||||
cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
raw, err := rpcClient.Dispense("script-executor")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
executor, ok := raw.(ScriptExecutor)
|
||||
if !ok {
|
||||
err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return executor, cleanup, nil
|
||||
}
|
364
internal/build/script_executor.go
Normal file
364
internal/build/script_executor.go
Normal file
@ -0,0 +1,364 @@
|
||||
// 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) // Определяем путь к пакету
|
||||
|
||||
pkgFile, err := os.Create(pkgPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = packager.Package(pkgInfo, pkgFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
86
internal/build/source_downloader.go
Normal file
86
internal/build/source_downloader.go
Normal file
@ -0,0 +1,86 @@
|
||||
// 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/dl"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/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
|
||||
}
|
||||
}
|
||||
|
||||
opts.DlCache = dlcache.New(s.cfg)
|
||||
|
||||
err := dl.Download(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
314
internal/build/utils.go
Normal file
314
internal/build/utils.go
Normal file
@ -0,0 +1,314 @@
|
||||
// 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"
|
||||
"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/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 {
|
||||
err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(dirs.SrcDir, 0o755) // Создаем директорию для источников
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов
|
||||
}
|
||||
|
||||
// Функция 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>[^+]+)\+alr-(?P<repo>.+)$`)
|
||||
|
||||
func getBasePkgInfo(vars *alrsh.Package, input interface {
|
||||
RepositoryProvider
|
||||
OsInfoProvider
|
||||
},
|
||||
) *nfpm.Info {
|
||||
return &nfpm.Info{
|
||||
Name: fmt.Sprintf("%s+alr-%s", vars.Name, input.Repository()),
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
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 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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 cliutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"plemya-x.ru/alr/internal/config"
|
||||
"plemya-x.ru/alr/internal/db"
|
||||
"plemya-x.ru/alr/internal/pager"
|
||||
"plemya-x.ru/alr/internal/translations"
|
||||
"plemya-x.ru/alr/pkg/loggerctx"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
)
|
||||
|
||||
// YesNoPrompt asks the user a yes or no question, using def as the default answer
|
||||
@ -37,7 +38,7 @@ func YesNoPrompt(ctx context.Context, msg string, interactive, def bool) (bool,
|
||||
var answer bool
|
||||
err := survey.AskOne(
|
||||
&survey.Confirm{
|
||||
Message: translations.Translator(ctx).TranslateTo(msg, config.Language(ctx)),
|
||||
Message: msg,
|
||||
Default: def,
|
||||
},
|
||||
&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
|
||||
// continue, and exits if they answer no.
|
||||
func PromptViewScript(ctx context.Context, script, name, style string, interactive bool) error {
|
||||
log := loggerctx.From(ctx)
|
||||
|
||||
if !interactive {
|
||||
return nil
|
||||
}
|
||||
|
||||
scriptPrompt := translations.Translator(ctx).TranslateTo("Would you like to view the build script for", config.Language(ctx)) + " " + name
|
||||
view, err := YesNoPrompt(ctx, scriptPrompt, interactive, false)
|
||||
view, err := YesNoPrompt(ctx, gotext.Get("Would you like to view the build script for %s", name), interactive, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -70,13 +68,14 @@ func PromptViewScript(ctx context.Context, script, name, style string, interacti
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
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,14 +102,14 @@ func ShowScript(path, name, style string) error {
|
||||
|
||||
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
|
||||
// of packages by prompting the user if multiple packages match.
|
||||
func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string, interactive bool) []db.Package {
|
||||
log := loggerctx.From(ctx)
|
||||
var outPkgs []db.Package
|
||||
func FlattenPkgs(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool) []alrsh.Package {
|
||||
var outPkgs []alrsh.Package
|
||||
for _, pkgs := range found {
|
||||
if len(pkgs) > 1 && interactive {
|
||||
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
|
||||
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)
|
||||
} else if len(pkgs) == 1 || !interactive {
|
||||
@ -121,7 +120,7 @@ func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string
|
||||
}
|
||||
|
||||
// PkgPrompt asks the user to choose between multiple packages.
|
||||
func PkgPrompt(ctx context.Context, options []db.Package, verb string, interactive bool) (db.Package, error) {
|
||||
func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, interactive bool) (alrsh.Package, error) {
|
||||
if !interactive {
|
||||
return options[0], nil
|
||||
}
|
||||
@ -133,13 +132,13 @@ func PkgPrompt(ctx context.Context, options []db.Package, verb string, interacti
|
||||
|
||||
prompt := &survey.Select{
|
||||
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
|
||||
err := survey.AskOne(prompt, &choice)
|
||||
if err != nil {
|
||||
return db.Package{}, err
|
||||
return alrsh.Package{}, err
|
||||
}
|
||||
|
||||
return options[choice], nil
|
||||
@ -154,7 +153,7 @@ func ChooseOptDepends(ctx context.Context, options []string, verb string, intera
|
||||
|
||||
prompt := &survey.MultiSelect{
|
||||
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
|
||||
|
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,161 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"sync"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
"github.com/caarlos0/env"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"plemya-x.ru/alr/internal/types"
|
||||
"plemya-x.ru/alr/pkg/loggerctx"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||
)
|
||||
|
||||
type ALRConfig struct {
|
||||
cfg *types.Config
|
||||
paths *Paths
|
||||
}
|
||||
|
||||
var defaultConfig = &types.Config{
|
||||
RootCmd: "sudo",
|
||||
UseRootCmd: true,
|
||||
PagerStyle: "native",
|
||||
IgnorePkgUpdates: []string{},
|
||||
Repos: []types.Repo{
|
||||
{
|
||||
Name: "default",
|
||||
URL: "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git",
|
||||
},
|
||||
},
|
||||
AutoPull: true,
|
||||
Repos: []types.Repo{},
|
||||
}
|
||||
|
||||
var (
|
||||
configMtx sync.Mutex
|
||||
config *types.Config
|
||||
)
|
||||
func New() *ALRConfig {
|
||||
return &ALRConfig{}
|
||||
}
|
||||
|
||||
// Config returns a ALR configuration struct.
|
||||
// The first time it's called, it'll load the config from a file.
|
||||
// Subsequent calls will just return the same value.
|
||||
func Config(ctx context.Context) *types.Config {
|
||||
configMtx.Lock()
|
||||
defer configMtx.Unlock()
|
||||
log := loggerctx.From(ctx)
|
||||
func readConfig(path string) (*types.Config, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if config == nil {
|
||||
cfgFl, err := os.Open(GetPaths(ctx).ConfigPath)
|
||||
if err != nil {
|
||||
log.Warn("Error opening config file, using defaults").Err(err).Send()
|
||||
return defaultConfig
|
||||
}
|
||||
defer cfgFl.Close()
|
||||
config := types.Config{}
|
||||
|
||||
// Copy the default configuration into config
|
||||
defCopy := *defaultConfig
|
||||
config = &defCopy
|
||||
config.Repos = nil
|
||||
|
||||
err = toml.NewDecoder(cfgFl).Decode(config)
|
||||
if err != nil {
|
||||
log.Warn("Error decoding config file, using defaults").Err(err).Send()
|
||||
// Set config back to nil so that we try again next time
|
||||
config = nil
|
||||
return defaultConfig
|
||||
}
|
||||
if err := toml.NewDecoder(file).Decode(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func mergeStructs(dst, src interface{}) {
|
||||
srcVal := reflect.ValueOf(src)
|
||||
if srcVal.IsNil() {
|
||||
return
|
||||
}
|
||||
srcVal = srcVal.Elem()
|
||||
dstVal := reflect.ValueOf(dst).Elem()
|
||||
|
||||
for i := range srcVal.NumField() {
|
||||
srcField := srcVal.Field(i)
|
||||
srcFieldName := srcVal.Type().Field(i).Name
|
||||
|
||||
dstField := dstVal.FieldByName(srcFieldName)
|
||||
if dstField.IsValid() && dstField.CanSet() {
|
||||
dstField.Set(srcField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ALRConfig) Load() error {
|
||||
systemConfig, err := readConfig(
|
||||
constants.SystemConfigPath,
|
||||
)
|
||||
if err != nil {
|
||||
slog.Debug("Cannot read system config", "err", err)
|
||||
}
|
||||
|
||||
config := &types.Config{}
|
||||
|
||||
mergeStructs(config, defaultConfig)
|
||||
mergeStructs(config, systemConfig)
|
||||
err = env.Parse(config)
|
||||
if err != nil {
|
||||
return 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(c.paths.CacheDir, "pkgs")
|
||||
c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db")
|
||||
// c.initPaths()
|
||||
|
||||
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.cfg.Repos = repos
|
||||
}
|
||||
|
||||
func (c *ALRConfig) IgnorePkgUpdates() []string {
|
||||
return c.cfg.IgnorePkgUpdates
|
||||
}
|
||||
|
||||
func (c *ALRConfig) LogLevel() string {
|
||||
return c.cfg.LogLevel
|
||||
}
|
||||
|
||||
func (c *ALRConfig) UseRootCmd() bool {
|
||||
return c.cfg.UseRootCmd
|
||||
}
|
||||
|
||||
func (c *ALRConfig) GetPaths() *Paths {
|
||||
return c.paths
|
||||
}
|
||||
|
||||
func (c *ALRConfig) SaveUserConfig() error {
|
||||
f, err := os.Create(c.paths.UserConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return toml.NewEncoder(f).Encode(c.cfg)
|
||||
}
|
||||
|
@ -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,29 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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
|
||||
|
||||
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
|
||||
type Paths struct {
|
||||
ConfigDir string
|
||||
ConfigPath string
|
||||
CacheDir string
|
||||
RepoDir string
|
||||
PkgsDir 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
|
||||
UserConfigPath string
|
||||
CacheDir string
|
||||
RepoDir string
|
||||
PkgsDir string
|
||||
DBPath string
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
// Version contains the version of ALR. If the version
|
||||
|
24
internal/constants/constants.go
Normal file
24
internal/constants/constants.go
Normal file
@ -0,0 +1,24 @@
|
||||
// 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"
|
||||
AlrRunDir = "/var/run/alr"
|
||||
PrivilegedGroup = "wheel"
|
||||
)
|
@ -1,20 +1,21 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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 cpu
|
||||
|
||||
@ -37,11 +38,12 @@ func armVariant() string {
|
||||
return armEnv
|
||||
}
|
||||
|
||||
if cpu.ARM.HasVFPv3 {
|
||||
switch {
|
||||
case cpu.ARM.HasVFPv3:
|
||||
return "arm7"
|
||||
} else if cpu.ARM.HasVFP {
|
||||
case cpu.ARM.HasVFP:
|
||||
return "arm6"
|
||||
} else {
|
||||
default:
|
||||
return "arm5"
|
||||
}
|
||||
}
|
||||
|
@ -1,346 +1,151 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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 db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"log/slog"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"plemya-x.ru/alr/internal/config"
|
||||
"plemya-x.ru/alr/pkg/loggerctx"
|
||||
"golang.org/x/exp/slices"
|
||||
"modernc.org/sqlite"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
_ "modernc.org/sqlite"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
)
|
||||
|
||||
// CurrentVersion is the current version of the database.
|
||||
// The database is reset if its version doesn't match this.
|
||||
const CurrentVersion = 2
|
||||
const CurrentVersion = 5
|
||||
|
||||
func init() {
|
||||
sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains)
|
||||
type Version struct {
|
||||
Version int `xorm:"'version'"`
|
||||
}
|
||||
|
||||
// Package is a ALR package's database representation
|
||||
type Package struct {
|
||||
Name string `sh:"name,required" db:"name"`
|
||||
Version string `sh:"version,required" db:"version"`
|
||||
Release int `sh:"release,required" db:"release"`
|
||||
Epoch uint `sh:"epoch" db:"epoch"`
|
||||
Description JSON[map[string]string] `db:"description"`
|
||||
Homepage JSON[map[string]string] `db:"homepage"`
|
||||
Maintainer JSON[map[string]string] `db:"maintainer"`
|
||||
Architectures JSON[[]string] `sh:"architectures" db:"architectures"`
|
||||
Licenses JSON[[]string] `sh:"license" db:"licenses"`
|
||||
Provides JSON[[]string] `sh:"provides" db:"provides"`
|
||||
Conflicts JSON[[]string] `sh:"conflicts" db:"conflicts"`
|
||||
Replaces JSON[[]string] `sh:"replaces" db:"replaces"`
|
||||
Depends JSON[map[string][]string] `db:"depends"`
|
||||
BuildDepends JSON[map[string][]string] `db:"builddepends"`
|
||||
OptDepends JSON[map[string][]string] `db:"optdepends"`
|
||||
Repository string `db:"repository"`
|
||||
type Config interface {
|
||||
GetPaths() *config.Paths
|
||||
}
|
||||
|
||||
type version struct {
|
||||
Version int `db:"version"`
|
||||
type Database struct {
|
||||
engine *xorm.Engine
|
||||
config Config
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
conn *sqlx.DB
|
||||
closed = true
|
||||
)
|
||||
|
||||
// DB returns the ALR database.
|
||||
// The first time it's called, it opens the SQLite database file.
|
||||
// Subsequent calls return the same connection.
|
||||
func DB(ctx context.Context) *sqlx.DB {
|
||||
log := loggerctx.From(ctx)
|
||||
if conn != nil && !closed {
|
||||
return getConn()
|
||||
}
|
||||
_, err := open(ctx, config.GetPaths(ctx).DBPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening database").Err(err).Send()
|
||||
}
|
||||
return getConn()
|
||||
}
|
||||
|
||||
func getConn() *sqlx.DB {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return conn
|
||||
}
|
||||
|
||||
func open(ctx context.Context, dsn string) (*sqlx.DB, error) {
|
||||
db, err := sqlx.Open("sqlite", dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
conn = db
|
||||
closed = false
|
||||
mu.Unlock()
|
||||
|
||||
err = initDB(ctx, dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Close closes the database
|
||||
func Close() error {
|
||||
closed = true
|
||||
if conn != nil {
|
||||
return conn.Close()
|
||||
} else {
|
||||
return nil
|
||||
func New(config Config) *Database {
|
||||
return &Database{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// initDB initializes the database
|
||||
func initDB(ctx context.Context, dsn string) error {
|
||||
log := loggerctx.From(ctx)
|
||||
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
|
||||
);
|
||||
`)
|
||||
func (d *Database) Connect() error {
|
||||
dsn := d.config.GetPaths().DBPath
|
||||
engine, err := xorm.NewEngine("sqlite", dsn)
|
||||
// engine.SetLogLevel(log.LOG_DEBUG)
|
||||
// engine.ShowSQL(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ver, ok := GetVersion(ctx)
|
||||
if ok && ver != CurrentVersion {
|
||||
log.Warn("Database version mismatch; resetting").Int("version", ver).Int("expected", CurrentVersion).Send()
|
||||
reset(ctx)
|
||||
return initDB(ctx, dsn)
|
||||
} else if !ok {
|
||||
log.Warn("Database version does not exist. Run alr fix if something isn't working.").Send()
|
||||
return addVersion(ctx, CurrentVersion)
|
||||
}
|
||||
|
||||
d.engine = engine
|
||||
return nil
|
||||
}
|
||||
|
||||
// reset drops all the database tables
|
||||
func reset(ctx context.Context) error {
|
||||
_, err := DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;")
|
||||
func (d *Database) Init(ctx context.Context) error {
|
||||
if err := d.Connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.engine.Sync2(new(alrsh.Package), new(Version)); err != nil {
|
||||
return err
|
||||
}
|
||||
ver, ok := d.GetVersion(ctx)
|
||||
if ok && ver != CurrentVersion {
|
||||
slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion)
|
||||
if err := d.reset(); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.Init(ctx)
|
||||
} 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
|
||||
}
|
||||
|
||||
func (d *Database) GetVersion(ctx context.Context) (int, bool) {
|
||||
var v Version
|
||||
has, err := d.engine.Get(&v)
|
||||
if err != nil || !has {
|
||||
return 0, false
|
||||
}
|
||||
return v.Version, true
|
||||
}
|
||||
|
||||
func (d *Database) addVersion(ver int) error {
|
||||
_, err := d.engine.Insert(&Version{Version: ver})
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) reset() error {
|
||||
return d.engine.DropTables(new(alrsh.Package), new(Version))
|
||||
}
|
||||
|
||||
func (d *Database) InsertPackage(ctx context.Context, pkg alrsh.Package) error {
|
||||
session := d.engine.Context(ctx)
|
||||
|
||||
affected, err := session.Where("name = ? AND repository = ?", pkg.Name, pkg.Repository).Update(&pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = 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 ver.Version, true
|
||||
}
|
||||
|
||||
func addVersion(ctx context.Context, ver int) error {
|
||||
_, err := DB(ctx).ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver)
|
||||
return err
|
||||
}
|
||||
|
||||
// InsertPackage adds a package to the database
|
||||
func InsertPackage(ctx context.Context, pkg Package) error {
|
||||
_, err := DB(ctx).NamedExecContext(ctx, `
|
||||
INSERT OR REPLACE INTO pkgs (
|
||||
name,
|
||||
repository,
|
||||
version,
|
||||
release,
|
||||
epoch,
|
||||
description,
|
||||
homepage,
|
||||
maintainer,
|
||||
architectures,
|
||||
licenses,
|
||||
provides,
|
||||
conflicts,
|
||||
replaces,
|
||||
depends,
|
||||
builddepends,
|
||||
optdepends
|
||||
) VALUES (
|
||||
:name,
|
||||
:repository,
|
||||
:version,
|
||||
:release,
|
||||
:epoch,
|
||||
:description,
|
||||
:homepage,
|
||||
:maintainer,
|
||||
:architectures,
|
||||
:licenses,
|
||||
:provides,
|
||||
:conflicts,
|
||||
:replaces,
|
||||
:depends,
|
||||
:builddepends,
|
||||
:optdepends
|
||||
);
|
||||
`, pkg)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPkgs returns a result containing packages that match the where conditions
|
||||
func GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) {
|
||||
stream, err := DB(ctx).QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
// GetPkg returns a single package that matches the where conditions
|
||||
func GetPkg(ctx context.Context, where string, args ...any) (*Package, error) {
|
||||
out := &Package{}
|
||||
err := DB(ctx).GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// DeletePkgs deletes all packages matching the where conditions
|
||||
func DeletePkgs(ctx context.Context, where string, args ...any) error {
|
||||
_, err := DB(ctx).ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
// jsonArrayContains is an SQLite function that checks if a JSON array
|
||||
// in the database contains a given value
|
||||
func jsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
|
||||
value, ok := args[0].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("both arguments to json_array_contains must be strings")
|
||||
}
|
||||
|
||||
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 affected == 0 {
|
||||
_, err = session.Insert(&pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case sql.NullString:
|
||||
if val.Valid {
|
||||
err := json.Unmarshal([]byte(val.String), &s.Val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return errors.New("sqlite json types must be strings")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s JSON[T]) Value() (driver.Value, error) {
|
||||
data, err := json.Marshal(s.Val)
|
||||
if err != nil {
|
||||
func (d *Database) GetPkgs(_ context.Context, where string, args ...any) ([]alrsh.Package, error) {
|
||||
var pkgs []alrsh.Package
|
||||
err := d.engine.Where(where, args...).Find(&pkgs)
|
||||
return pkgs, err
|
||||
}
|
||||
|
||||
func (d *Database) GetPkg(where string, args ...any) (*alrsh.Package, error) {
|
||||
var pkg alrsh.Package
|
||||
has, err := d.engine.Where(where, args...).Get(&pkg)
|
||||
if err != nil || !has {
|
||||
return nil, err
|
||||
}
|
||||
return string(data), nil
|
||||
return &pkg, nil
|
||||
}
|
||||
|
||||
func (s JSON[T]) MarshalYAML() (any, error) {
|
||||
return s.Val, nil
|
||||
func (d *Database) DeletePkgs(_ context.Context, where string, args ...any) error {
|
||||
_, err := d.engine.Where(where, args...).Delete(&alrsh.Package{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s JSON[T]) String() string {
|
||||
return fmt.Sprint(s.Val)
|
||||
func (d *Database) IsEmpty() bool {
|
||||
count, err := d.engine.Count(new(alrsh.Package))
|
||||
return err != nil || count == 0
|
||||
}
|
||||
|
||||
func (s JSON[T]) GoString() string {
|
||||
return fmt.Sprintf("%#v", s.Val)
|
||||
func (d *Database) Close() error {
|
||||
return d.engine.Close()
|
||||
}
|
||||
|
@ -1,76 +1,91 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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 db_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"plemya-x.ru/alr/internal/db"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
)
|
||||
|
||||
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",
|
||||
Version: "0.0.1",
|
||||
Release: 1,
|
||||
Epoch: 2,
|
||||
Description: db.NewJSON(map[string]string{
|
||||
Description: alrsh.OverridableFromMap(map[string]string{
|
||||
"en": "Test package",
|
||||
"ru": "Проверочный пакет",
|
||||
}),
|
||||
Homepage: db.NewJSON(map[string]string{
|
||||
Homepage: alrsh.OverridableFromMap(map[string]string{
|
||||
"en": "https://gitea.plemya-x.ru/xpamych/ALR",
|
||||
}),
|
||||
Maintainer: db.NewJSON(map[string]string{
|
||||
Maintainer: alrsh.OverridableFromMap(map[string]string{
|
||||
"en": "Evgeniy Khramov <xpamych@yandex.ru>",
|
||||
"ru": "Евгений Храмов <xpamych@yandex.ru>",
|
||||
}),
|
||||
Architectures: db.NewJSON([]string{"arm64", "amd64"}),
|
||||
Licenses: db.NewJSON([]string{"GPL-3.0-or-later"}),
|
||||
Provides: db.NewJSON([]string{"test"}),
|
||||
Conflicts: db.NewJSON([]string{"test"}),
|
||||
Replaces: db.NewJSON([]string{"test-old"}),
|
||||
Depends: db.NewJSON(map[string][]string{
|
||||
Architectures: []string{"arm64", "amd64"},
|
||||
Licenses: []string{"GPL-3.0-or-later"},
|
||||
Provides: []string{"test"},
|
||||
Conflicts: []string{"test"},
|
||||
Replaces: []string{"test-old"},
|
||||
Depends: alrsh.OverridableFromMap(map[string][]string{
|
||||
"": {"sudo"},
|
||||
}),
|
||||
BuildDepends: db.NewJSON(map[string][]string{
|
||||
BuildDepends: alrsh.OverridableFromMap(map[string][]string{
|
||||
"": {"golang"},
|
||||
"arch": {"go"},
|
||||
}),
|
||||
Repository: "default",
|
||||
Summary: alrsh.OverridableFromMap(map[string]string{}),
|
||||
Group: alrsh.OverridableFromMap(map[string]string{}),
|
||||
OptDepends: alrsh.OverridableFromMap(map[string][]string{}),
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer db.Close()
|
||||
ctx := context.Background()
|
||||
database := prepareDb()
|
||||
defer database.Close()
|
||||
|
||||
_, err = db.DB().Exec("SELECT * FROM pkgs")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
ver, ok := db.GetVersion()
|
||||
ver, ok := database.GetVersion(ctx)
|
||||
if !ok {
|
||||
t.Errorf("Expected version to be present")
|
||||
} else if ver != db.CurrentVersion {
|
||||
@ -79,62 +94,53 @@ func TestInit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInsertPackage(t *testing.T) {
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer db.Close()
|
||||
ctx := context.Background()
|
||||
database := prepareDb()
|
||||
defer database.Close()
|
||||
|
||||
err = db.InsertPackage(testPkg)
|
||||
err := database.InsertPackage(ctx, testPkg)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
dbPkg := db.Package{}
|
||||
err = sqlx.Get(db.DB(), &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'")
|
||||
pkgs, err := database.GetPkgs(ctx, "name = 'test' AND repository = 'default'")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(testPkg, dbPkg) {
|
||||
t.Errorf("Expected test package to be the same as database package")
|
||||
if len(pkgs) != 1 {
|
||||
t.Fatalf("Expected 1 package, got %d", len(pkgs))
|
||||
}
|
||||
|
||||
assert.Equal(t, testPkg, pkgs[0])
|
||||
}
|
||||
|
||||
func TestGetPkgs(t *testing.T) {
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer db.Close()
|
||||
ctx := context.Background()
|
||||
database := prepareDb()
|
||||
defer database.Close()
|
||||
|
||||
x1 := testPkg
|
||||
x1.Name = "x1"
|
||||
x2 := testPkg
|
||||
x2.Name = "x2"
|
||||
|
||||
err = db.InsertPackage(x1)
|
||||
err := database.InsertPackage(ctx, x1)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.InsertPackage(x2)
|
||||
err = database.InsertPackage(ctx, x2)
|
||||
if err != nil {
|
||||
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 {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
for result.Next() {
|
||||
var dbPkg db.Package
|
||||
err = result.StructScan(&dbPkg)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
for _, dbPkg := range pkgs {
|
||||
if !strings.HasPrefix(dbPkg.Name, "x") {
|
||||
t.Errorf("Expected package name to start with 'x', got %s", dbPkg.Name)
|
||||
}
|
||||
@ -142,28 +148,26 @@ func TestGetPkgs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetPkg(t *testing.T) {
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer db.Close()
|
||||
ctx := context.Background()
|
||||
database := prepareDb()
|
||||
defer database.Close()
|
||||
|
||||
x1 := testPkg
|
||||
x1.Name = "x1"
|
||||
x2 := testPkg
|
||||
x2.Name = "x2"
|
||||
|
||||
err = db.InsertPackage(x1)
|
||||
err := database.InsertPackage(ctx, x1)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.InsertPackage(x2)
|
||||
err = database.InsertPackage(ctx, x2)
|
||||
if err != nil {
|
||||
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 {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
@ -178,73 +182,70 @@ func TestGetPkg(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeletePkgs(t *testing.T) {
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer db.Close()
|
||||
ctx := context.Background()
|
||||
database := prepareDb()
|
||||
defer database.Close()
|
||||
|
||||
x1 := testPkg
|
||||
x1.Name = "x1"
|
||||
x2 := testPkg
|
||||
x2.Name = "x2"
|
||||
|
||||
err = db.InsertPackage(x1)
|
||||
err := database.InsertPackage(ctx, x1)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.InsertPackage(x2)
|
||||
err = database.InsertPackage(ctx, x2)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.DeletePkgs("name = 'x1'")
|
||||
err = database.DeletePkgs(ctx, "name = 'x1'")
|
||||
if err != nil {
|
||||
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) {
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer db.Close()
|
||||
ctx := context.Background()
|
||||
database := prepareDb()
|
||||
defer database.Close()
|
||||
|
||||
x1 := testPkg
|
||||
x1.Name = "x1"
|
||||
x2 := testPkg
|
||||
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 {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.InsertPackage(x2)
|
||||
err = database.InsertPackage(ctx, x2)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
var dbPkg db.Package
|
||||
err = db.DB().Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');")
|
||||
pkgs, err := database.GetPkgs(ctx, "name = 'x2'")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
if dbPkg.Name != "x2" {
|
||||
t.Errorf("Expected x2 package, got %s", dbPkg.Name)
|
||||
if len(pkgs) != 1 || pkgs[0].Name != "x2" {
|
||||
t.Errorf("Expected x2 package, got %v", pkgs)
|
||||
}
|
||||
|
||||
// Verify the provides field contains 'x'
|
||||
found := false
|
||||
for _, p := range pkgs[0].Provides {
|
||||
if p == "x" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected provides to contain 'x'")
|
||||
}
|
||||
}
|
||||
|
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,20 +1,21 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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/>.
|
||||
|
||||
// Пакет dl содержит абстракции для загрузки файлов и каталогов
|
||||
// из различных источников.
|
||||
@ -30,17 +31,17 @@ import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/purell"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
"golang.org/x/crypto/blake2s"
|
||||
"golang.org/x/exp/slices"
|
||||
"plemya-x.ru/alr/internal/dlcache"
|
||||
"plemya-x.ru/alr/pkg/loggerctx"
|
||||
)
|
||||
|
||||
// Константа для имени файла манифеста кэша
|
||||
@ -79,6 +80,11 @@ func (t Type) String() string {
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
type DlCache interface {
|
||||
Get(context.Context, string) (string, bool)
|
||||
New(context.Context, string) (string, error)
|
||||
}
|
||||
|
||||
// Структура Options содержит параметры для загрузки файлов и каталогов
|
||||
type Options struct {
|
||||
Hash []byte
|
||||
@ -90,6 +96,7 @@ type Options struct {
|
||||
PostprocDisabled bool
|
||||
Progress io.Writer
|
||||
LocalDir string
|
||||
DlCache DlCache
|
||||
}
|
||||
|
||||
// Метод для создания нового хеша на основе указанного алгоритма хеширования
|
||||
@ -130,7 +137,7 @@ type Manifest struct {
|
||||
type Downloader interface {
|
||||
Name() string
|
||||
MatchURL(string) bool
|
||||
Download(Options) (Type, string, error)
|
||||
Download(context.Context, Options) (Type, string, error)
|
||||
}
|
||||
|
||||
// Интерфейс UpdatingDownloader расширяет Downloader методом Update
|
||||
@ -141,7 +148,6 @@ type UpdatingDownloader interface {
|
||||
|
||||
// Функция Download загружает файл или каталог с использованием указанных параметров
|
||||
func Download(ctx context.Context, opts Options) (err error) {
|
||||
log := loggerctx.From(ctx)
|
||||
normalized, err := normalizeURL(opts.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -151,16 +157,20 @@ func Download(ctx context.Context, opts Options) (err error) {
|
||||
d := getDownloader(opts.URL)
|
||||
|
||||
if opts.CacheDisabled {
|
||||
_, _, err = d.Download(opts)
|
||||
_, _, err = d.Download(ctx, opts)
|
||||
return err
|
||||
}
|
||||
|
||||
var t Type
|
||||
cacheDir, ok := dlcache.Get(ctx, opts.URL)
|
||||
cacheDir, ok := opts.DlCache.Get(ctx, opts.URL)
|
||||
if ok {
|
||||
var updated bool
|
||||
if d, ok := d.(UpdatingDownloader); ok {
|
||||
log.Info("Source can be updated, updating if required").Str("source", opts.Name).Str("downloader", d.Name()).Send()
|
||||
slog.Info(
|
||||
gotext.Get("Source can be updated, updating if required"),
|
||||
"source", opts.Name,
|
||||
"downloader", d.Name(),
|
||||
)
|
||||
|
||||
updated, err = d.Update(Options{
|
||||
Hash: opts.Hash,
|
||||
@ -187,10 +197,18 @@ func Download(ctx context.Context, opts Options) (err error) {
|
||||
}
|
||||
|
||||
if ok && !updated {
|
||||
log.Info("Source found in cache and linked to destination").Str("source", opts.Name).Stringer("type", t).Send()
|
||||
slog.Info(
|
||||
gotext.Get("Source found in cache and linked to destination"),
|
||||
"source", opts.Name,
|
||||
"type", t,
|
||||
)
|
||||
return nil
|
||||
} else if ok {
|
||||
log.Info("Source updated and linked to destination").Str("source", opts.Name).Stringer("type", t).Send()
|
||||
slog.Info(
|
||||
gotext.Get("Source updated and linked to destination"),
|
||||
"source", opts.Name,
|
||||
"type", t,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
@ -201,14 +219,14 @@ func Download(ctx context.Context, opts Options) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Downloading source").Str("source", opts.Name).Str("downloader", d.Name()).Send()
|
||||
slog.Info(gotext.Get("Downloading source"), "source", opts.Name, "downloader", d.Name())
|
||||
|
||||
cacheDir, err = dlcache.New(ctx, opts.URL)
|
||||
cacheDir, err = opts.DlCache.New(ctx, opts.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t, name, err := d.Download(Options{
|
||||
t, name, err := d.Download(ctx, Options{
|
||||
Hash: opts.Hash,
|
||||
HashAlgorithm: opts.HashAlgorithm,
|
||||
Name: opts.Name,
|
||||
@ -299,8 +317,6 @@ func linkDir(src, dest string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
rel, err := filepath.Rel(src, path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
176
internal/dl/dl_test.go
Normal file
176
internal/dl/dl_test.go
Normal file
@ -0,0 +1,176 @@
|
||||
// ALR - Any Linux Repository
|
||||
// Copyright (C) 2025 The ALR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package dl_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
|
||||
)
|
||||
|
||||
type TestALRConfig struct{}
|
||||
|
||||
func (c *TestALRConfig) GetPaths() *config.Paths {
|
||||
return &config.Paths{
|
||||
CacheDir: "/tmp",
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadWithoutCache(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
path string
|
||||
expected func(*testing.T, error, string)
|
||||
}
|
||||
|
||||
prepareServer := func() *httptest.Server {
|
||||
// URL вашего Git-сервера
|
||||
gitServerURL, err := url.Parse("https://gitea.plemya-x.ru")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse git server URL: %v", err)
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(gitServerURL)
|
||||
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.URL.Path == "/file-downloader/file":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("Hello, World!"))
|
||||
case strings.HasPrefix(r.URL.Path, "/git-downloader/git"):
|
||||
r.URL.Host = gitServerURL.Host
|
||||
r.URL.Scheme = gitServerURL.Scheme
|
||||
r.Host = gitServerURL.Host
|
||||
r.URL.Path, _ = strings.CutPrefix(r.URL.Path, "/git-downloader/git")
|
||||
|
||||
proxy.ServeHTTP(w, r)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
for _, tc := range []testCase{
|
||||
{
|
||||
name: "simple file download",
|
||||
path: "%s/file-downloader/file",
|
||||
expected: func(t *testing.T, err error, tmpdir string) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = os.Stat(path.Join(tmpdir, "file"))
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "git download",
|
||||
path: "git+%s/git-downloader/git/Plemya-x/alr-repo",
|
||||
expected: func(t *testing.T, err error, tmpdir string) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = os.Stat(path.Join(tmpdir, "alr-repo.toml"))
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
server := prepareServer()
|
||||
defer server.Close()
|
||||
|
||||
tmpdir, err := os.MkdirTemp("", "test-download")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
opts := dl.Options{
|
||||
CacheDisabled: true,
|
||||
URL: fmt.Sprintf(tc.path, server.URL),
|
||||
Destination: tmpdir,
|
||||
}
|
||||
|
||||
err = dl.Download(context.Background(), opts)
|
||||
|
||||
tc.expected(t, err, tmpdir)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadFileWithCache(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
}
|
||||
|
||||
for _, tc := range []testCase{
|
||||
{
|
||||
name: "simple download",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
called := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.URL.Path == "/file":
|
||||
called += 1
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("Hello, World!"))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpdir, err := os.MkdirTemp("", "test-download")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
cfg := &TestALRConfig{}
|
||||
|
||||
opts := dl.Options{
|
||||
CacheDisabled: false,
|
||||
URL: server.URL + "/file",
|
||||
Destination: tmpdir,
|
||||
DlCache: dlcache.New(cfg),
|
||||
}
|
||||
|
||||
outputFile := path.Join(tmpdir, "file")
|
||||
|
||||
err = dl.Download(context.Background(), opts)
|
||||
assert.NoError(t, err)
|
||||
_, err = os.Stat(outputFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.Remove(outputFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = dl.Download(context.Background(), opts)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, called)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,26 +1,28 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
|
||||
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
|
||||
//
|
||||
// ALR - Any Linux Repository
|
||||
// Copyright (C) 2025 The ALR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package dl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
@ -29,11 +31,8 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/archiver/v4"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"plemya-x.ru/alr/internal/shutils/handlers"
|
||||
)
|
||||
|
||||
// FileDownloader загружает файлы с использованием HTTP
|
||||
@ -52,7 +51,7 @@ func (FileDownloader) MatchURL(string) bool {
|
||||
|
||||
// Download загружает файл с использованием HTTP. Если файл
|
||||
// сжат в поддерживаемом формате, он будет распакован
|
||||
func (FileDownloader) Download(opts Options) (Type, string, error) {
|
||||
func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string, error) {
|
||||
// Разбор URL
|
||||
u, err := url.Parse(opts.URL)
|
||||
if err != nil {
|
||||
@ -92,8 +91,12 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
|
||||
}
|
||||
r = localFl
|
||||
} else {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return 0, "", fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
// Выполнение HTTP GET запроса
|
||||
res, err := http.Get(u.String())
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
@ -112,30 +115,15 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
defer fl.Close()
|
||||
|
||||
var bar io.WriteCloser
|
||||
var out io.WriteCloser
|
||||
// Настройка индикатора прогресса
|
||||
if opts.Progress != nil {
|
||||
bar = progressbar.NewOptions64(
|
||||
size,
|
||||
progressbar.OptionSetDescription(name),
|
||||
progressbar.OptionSetWriter(opts.Progress),
|
||||
progressbar.OptionShowBytes(true),
|
||||
progressbar.OptionSetWidth(10),
|
||||
progressbar.OptionThrottle(65*time.Millisecond),
|
||||
progressbar.OptionShowCount(),
|
||||
progressbar.OptionOnCompletion(func() {
|
||||
_, _ = io.WriteString(opts.Progress, "\n")
|
||||
}),
|
||||
progressbar.OptionSpinnerType(14),
|
||||
progressbar.OptionFullWidth(),
|
||||
progressbar.OptionSetRenderBlankState(true),
|
||||
)
|
||||
defer bar.Close()
|
||||
out = NewProgressWriter(fl, size, name, opts.Progress)
|
||||
} else {
|
||||
bar = handlers.NopRWC{}
|
||||
out = fl
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
h, err := opts.NewHash()
|
||||
if err != nil {
|
||||
@ -145,9 +133,9 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
|
||||
var w io.Writer
|
||||
// Настройка MultiWriter для записи в файл, хеш и индикатор прогресса
|
||||
if opts.Hash != nil {
|
||||
w = io.MultiWriter(fl, h, bar)
|
||||
w = io.MultiWriter(h, out)
|
||||
} else {
|
||||
w = io.MultiWriter(fl, bar)
|
||||
w = io.MultiWriter(out)
|
||||
}
|
||||
|
||||
// Копирование содержимого из источника в файл назначения
|
||||
|
@ -1,24 +1,26 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
|
||||
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
|
||||
//
|
||||
// ALR - Any Linux Repository
|
||||
// Copyright (C) 2025 The ALR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package dl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"path"
|
||||
@ -46,7 +48,7 @@ func (GitDownloader) MatchURL(u string) bool {
|
||||
// Download uses git to clone the repository from the specified URL.
|
||||
// It allows specifying the revision, depth and recursion options
|
||||
// via query string
|
||||
func (GitDownloader) Download(opts Options) (Type, string, error) {
|
||||
func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, error) {
|
||||
u, err := url.Parse(opts.URL)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
@ -88,7 +90,7 @@ func (GitDownloader) Download(opts Options) (Type, string, error) {
|
||||
co.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
|
||||
}
|
||||
|
||||
r, err := git.PlainClone(opts.Destination, false, co)
|
||||
r, err := git.PlainCloneContext(ctx, opts.Destination, false, co)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
247
internal/dl/progress_tui.go
Normal file
247
internal/dl/progress_tui.go
Normal file
@ -0,0 +1,247 @@
|
||||
// ALR - Any Linux Repository
|
||||
// Copyright (C) 2025 The ALR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package dl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/progress"
|
||||
"github.com/charmbracelet/bubbles/spinner"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
type model struct {
|
||||
progress progress.Model
|
||||
spinner spinner.Model
|
||||
percent float64
|
||||
speed float64
|
||||
done bool
|
||||
useSpinner bool
|
||||
filename string
|
||||
|
||||
total int64
|
||||
downloaded int64
|
||||
elapsed time.Duration
|
||||
remaining time.Duration
|
||||
|
||||
width int
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
if m.useSpinner {
|
||||
return m.spinner.Tick
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.done {
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case progressUpdate:
|
||||
m.percent = msg.percent
|
||||
m.speed = msg.speed
|
||||
m.downloaded = msg.downloaded
|
||||
m.total = msg.total
|
||||
m.elapsed = time.Duration(msg.elapsed) * time.Second
|
||||
m.remaining = time.Duration(msg.remaining) * time.Second
|
||||
if m.percent >= 1.0 {
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
return m, nil
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
return m, nil
|
||||
case progress.FrameMsg:
|
||||
if !m.useSpinner {
|
||||
progressModel, cmd := m.progress.Update(msg)
|
||||
m.progress = progressModel.(progress.Model)
|
||||
return m, cmd
|
||||
}
|
||||
case spinner.TickMsg:
|
||||
if m.useSpinner {
|
||||
spinnerModel, cmd := m.spinner.Update(msg)
|
||||
m.spinner = spinnerModel
|
||||
return m, cmd
|
||||
}
|
||||
case tea.KeyMsg:
|
||||
if msg.String() == "q" {
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m model) View() string {
|
||||
if m.done {
|
||||
return gotext.Get("%s: done!\n", m.filename)
|
||||
}
|
||||
if m.useSpinner {
|
||||
return gotext.Get(
|
||||
"%s %s downloading at %s/s\n",
|
||||
m.filename,
|
||||
m.spinner.View(),
|
||||
prettyByteSize(int64(m.speed)),
|
||||
)
|
||||
}
|
||||
|
||||
leftPart := m.filename
|
||||
|
||||
rightPart := fmt.Sprintf("%.2f%% (%s/%s, %s/s) [%v:%v]\n", m.percent*100,
|
||||
prettyByteSize(m.downloaded),
|
||||
prettyByteSize(m.total),
|
||||
prettyByteSize(int64(m.speed)),
|
||||
m.elapsed,
|
||||
m.remaining,
|
||||
)
|
||||
|
||||
m.progress.Width = m.width - len(leftPart) - len(rightPart) - 6
|
||||
bar := m.progress.ViewAs(m.percent)
|
||||
return fmt.Sprintf(
|
||||
"%s %s %s",
|
||||
leftPart,
|
||||
bar,
|
||||
rightPart,
|
||||
)
|
||||
}
|
||||
|
||||
func prettyByteSize(b int64) string {
|
||||
bf := float64(b)
|
||||
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
|
||||
if math.Abs(bf) < 1024.0 {
|
||||
return fmt.Sprintf("%3.1f%sB", bf, unit)
|
||||
}
|
||||
bf /= 1024.0
|
||||
}
|
||||
return fmt.Sprintf("%.1fYiB", bf)
|
||||
}
|
||||
|
||||
type progressUpdate struct {
|
||||
percent float64
|
||||
speed float64
|
||||
total int64
|
||||
|
||||
downloaded int64
|
||||
elapsed float64
|
||||
remaining float64
|
||||
}
|
||||
|
||||
type ProgressWriter struct {
|
||||
baseWriter io.WriteCloser
|
||||
total int64
|
||||
downloaded int64
|
||||
startTime time.Time
|
||||
onProgress func(progressUpdate)
|
||||
lastReported time.Time
|
||||
doneChan chan struct{}
|
||||
}
|
||||
|
||||
func (pw *ProgressWriter) Write(p []byte) (int, error) {
|
||||
n, err := pw.baseWriter.Write(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
pw.downloaded += int64(n)
|
||||
now := time.Now()
|
||||
elapsed := now.Sub(pw.startTime).Seconds()
|
||||
speed := float64(pw.downloaded) / elapsed
|
||||
var remaining, percent float64
|
||||
if pw.total > 0 {
|
||||
remaining = (float64(pw.total) - float64(pw.downloaded)) / speed
|
||||
percent = float64(pw.downloaded) / float64(pw.total)
|
||||
}
|
||||
|
||||
if now.Sub(pw.lastReported) > 100*time.Millisecond {
|
||||
pw.onProgress(progressUpdate{
|
||||
percent: percent,
|
||||
speed: speed,
|
||||
total: pw.total,
|
||||
downloaded: pw.downloaded,
|
||||
elapsed: elapsed,
|
||||
remaining: remaining,
|
||||
})
|
||||
pw.lastReported = now
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (pw *ProgressWriter) Close() error {
|
||||
pw.onProgress(progressUpdate{
|
||||
percent: 1,
|
||||
speed: 0,
|
||||
downloaded: pw.downloaded,
|
||||
})
|
||||
<-pw.doneChan
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewProgressWriter(base io.WriteCloser, max int64, filename string, out io.Writer) *ProgressWriter {
|
||||
var m *model
|
||||
if max == -1 {
|
||||
m = &model{
|
||||
spinner: spinner.New(),
|
||||
useSpinner: true,
|
||||
filename: filename,
|
||||
}
|
||||
m.spinner.Spinner = spinner.Dot
|
||||
} else {
|
||||
m = &model{
|
||||
progress: progress.New(
|
||||
progress.WithDefaultGradient(),
|
||||
progress.WithoutPercentage(),
|
||||
),
|
||||
useSpinner: false,
|
||||
filename: filename,
|
||||
}
|
||||
}
|
||||
|
||||
p := tea.NewProgram(m,
|
||||
tea.WithInput(nil),
|
||||
tea.WithOutput(out),
|
||||
)
|
||||
|
||||
pw := &ProgressWriter{
|
||||
baseWriter: base,
|
||||
total: max,
|
||||
startTime: time.Now(),
|
||||
doneChan: make(chan struct{}),
|
||||
onProgress: func(update progressUpdate) {
|
||||
p.Send(update)
|
||||
},
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(pw.doneChan)
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error running progress writer: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
return pw
|
||||
}
|
@ -1,6 +1,26 @@
|
||||
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
|
||||
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
|
||||
//
|
||||
// ALR - Any Linux Repository
|
||||
// Copyright (C) 2025 The ALR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package dl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@ -30,7 +50,7 @@ func (TorrentDownloader) MatchURL(u string) bool {
|
||||
}
|
||||
|
||||
// Download downloads a file over the BitTorrent protocol.
|
||||
func (TorrentDownloader) Download(opts Options) (Type, string, error) {
|
||||
func (TorrentDownloader) Download(ctx context.Context, opts Options) (Type, string, error) {
|
||||
aria2Path, err := exec.LookPath("aria2c")
|
||||
if err != nil {
|
||||
return 0, "", ErrAria2NotFound
|
||||
@ -38,7 +58,7 @@ func (TorrentDownloader) Download(opts Options) (Type, string, error) {
|
||||
|
||||
opts.URL = strings.TrimPrefix(opts.URL, "torrent+")
|
||||
|
||||
cmd := exec.Command(aria2Path, "--summary-interval=0", "--log-level=warn", "--seed-time=0", "--dir="+opts.Destination, opts.URL)
|
||||
cmd := exec.CommandContext(ctx, aria2Path, "--summary-interval=0", "--log-level=warn", "--seed-time=0", "--dir="+opts.Destination, opts.URL)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
|
@ -1,48 +1,61 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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 dlcache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"plemya-x.ru/alr/internal/config"
|
||||
"gitea.plemya-x.ru/Plemya-x/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")
|
||||
type Config interface {
|
||||
GetPaths() *config.Paths
|
||||
}
|
||||
|
||||
type DownloadCache struct {
|
||||
cfg Config
|
||||
}
|
||||
|
||||
func New(cfg Config) *DownloadCache {
|
||||
return &DownloadCache{
|
||||
cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DownloadCache) BasePath(ctx context.Context) string {
|
||||
return filepath.Join(
|
||||
dc.cfg.GetPaths().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) {
|
||||
func (dc *DownloadCache) New(ctx context.Context, id string) (string, error) {
|
||||
h, err := hashID(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
itemPath := filepath.Join(BasePath(ctx), h)
|
||||
itemPath := filepath.Join(dc.BasePath(ctx), h)
|
||||
|
||||
fi, err := os.Stat(itemPath)
|
||||
if err == nil || (fi != nil && !fi.IsDir()) {
|
||||
@ -65,12 +78,12 @@ func New(ctx context.Context, id string) (string, error) {
|
||||
// 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) {
|
||||
func (dc *DownloadCache) Get(ctx context.Context, id string) (string, bool) {
|
||||
h, err := hashID(id)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
itemPath := filepath.Join(BasePath(ctx), h)
|
||||
itemPath := filepath.Join(dc.BasePath(ctx), h)
|
||||
|
||||
_, err = os.Stat(itemPath)
|
||||
if err != nil {
|
||||
@ -79,15 +92,3 @@ func Get(ctx context.Context, id string) (string, bool) {
|
||||
|
||||
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,20 +1,21 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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 dlcache_test
|
||||
|
||||
@ -27,26 +28,53 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"plemya-x.ru/alr/internal/config"
|
||||
"plemya-x.ru/alr/internal/dlcache"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
|
||||
)
|
||||
|
||||
func init() {
|
||||
type TestALRConfig struct {
|
||||
CacheDir string
|
||||
}
|
||||
|
||||
func (c *TestALRConfig) GetPaths() *config.Paths {
|
||||
return &config.Paths{
|
||||
CacheDir: c.CacheDir,
|
||||
}
|
||||
}
|
||||
|
||||
func prepare(t *testing.T) *TestALRConfig {
|
||||
t.Helper()
|
||||
|
||||
dir, err := os.MkdirTemp("/tmp", "alr-dlcache-test.*")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
config.GetPaths(context.Background()).RepoDir = dir
|
||||
|
||||
return &TestALRConfig{
|
||||
CacheDir: dir,
|
||||
}
|
||||
}
|
||||
|
||||
func cleanup(t *testing.T, cfg *TestALRConfig) {
|
||||
t.Helper()
|
||||
os.Remove(cfg.CacheDir)
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
cfg := prepare(t)
|
||||
defer cleanup(t, cfg)
|
||||
|
||||
dc := dlcache.New(cfg)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
const id = "https://example.com"
|
||||
dir, err := dlcache.New(id)
|
||||
dir, err := dc.New(ctx, id)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
exp := filepath.Join(dlcache.BasePath(), sha1sum(id))
|
||||
exp := filepath.Join(dc.BasePath(ctx), sha1sum(id))
|
||||
if dir != exp {
|
||||
t.Errorf("Expected %s, got %s", exp, dir)
|
||||
}
|
||||
@ -60,7 +88,7 @@ func TestNew(t *testing.T) {
|
||||
t.Errorf("Expected cache item to be a directory")
|
||||
}
|
||||
|
||||
dir2, ok := dlcache.Get(id)
|
||||
dir2, ok := dc.Get(ctx, id)
|
||||
if !ok {
|
||||
t.Errorf("Expected Get() to return valid value")
|
||||
}
|
||||
|
32
internal/dlcache/utils.go
Normal file
32
internal/dlcache/utils.go
Normal file
@ -0,0 +1,32 @@
|
||||
// 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 dlcache
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
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)
|
||||
}
|
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/*
|
||||
}
|
152
internal/logger/hclog.go
Normal file
152
internal/logger/hclog.go
Normal file
@ -0,0 +1,152 @@
|
||||
// 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, "[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
|
||||
}
|
@ -1,20 +1,21 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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 manager
|
||||
|
||||
@ -27,7 +28,15 @@ import (
|
||||
|
||||
// APK represents the APK package manager
|
||||
type APK struct {
|
||||
rootCmd string
|
||||
CommonPackageManager
|
||||
}
|
||||
|
||||
func NewAPK() *APK {
|
||||
return &APK{
|
||||
CommonPackageManager: CommonPackageManager{
|
||||
noConfirmArg: "-i",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (*APK) Exists() bool {
|
||||
@ -43,10 +52,6 @@ func (*APK) Format() string {
|
||||
return "apk"
|
||||
}
|
||||
|
||||
func (a *APK) SetRootCmd(s string) {
|
||||
a.rootCmd = s
|
||||
}
|
||||
|
||||
func (a *APK) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apk", "update")
|
||||
@ -148,19 +153,17 @@ func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
|
||||
cmd.Args = append(cmd.Args, opts.Args...)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
} else {
|
||||
cmd = exec.Command(mgrCmd, args...)
|
||||
func (a *APK) IsInstalled(pkg string) (bool, error) {
|
||||
cmd := exec.Command("apk", "info", "--installed", pkg)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
// Exit code 1 means the package is not installed
|
||||
if exitErr.ExitCode() == 1 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("apk: isinstalled: %w, output: %s", err, output)
|
||||
}
|
||||
|
||||
if !opts.NoConfirm {
|
||||
cmd.Args = append(cmd.Args, "-i")
|
||||
}
|
||||
|
||||
return cmd
|
||||
return true, nil
|
||||
}
|
@ -1,20 +1,21 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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 manager
|
||||
|
||||
@ -27,7 +28,15 @@ import (
|
||||
|
||||
// APT represents the APT package manager
|
||||
type APT struct {
|
||||
rootCmd string
|
||||
CommonPackageManager
|
||||
}
|
||||
|
||||
func NewAPT() *APT {
|
||||
return &APT{
|
||||
CommonPackageManager: CommonPackageManager{
|
||||
noConfirmArg: "-y",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (*APT) Exists() bool {
|
||||
@ -43,10 +52,6 @@ func (*APT) Format() string {
|
||||
return "deb"
|
||||
}
|
||||
|
||||
func (a *APT) SetRootCmd(s string) {
|
||||
a.rootCmd = s
|
||||
}
|
||||
|
||||
func (a *APT) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apt", "update")
|
||||
@ -134,19 +139,17 @@ func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
|
||||
cmd.Args = append(cmd.Args, opts.Args...)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
} else {
|
||||
cmd = exec.Command(mgrCmd, args...)
|
||||
func (a *APT) IsInstalled(pkg string) (bool, error) {
|
||||
cmd := exec.Command("dpkg-query", "-l", pkg)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
// Exit code 1 means the package is not installed
|
||||
if exitErr.ExitCode() == 1 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("apt: isinstalled: %w, output: %s", err, output)
|
||||
}
|
||||
|
||||
if opts.NoConfirm {
|
||||
cmd.Args = append(cmd.Args, "-y")
|
||||
}
|
||||
|
||||
return cmd
|
||||
return true, nil
|
||||
}
|
112
internal/manager/apt_rpm.go
Normal file
112
internal/manager/apt_rpm.go
Normal file
@ -0,0 +1,112 @@
|
||||
// ALR - Any Linux Repository
|
||||
// Copyright (C) 2025 The ALR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// APTRpm represents the APT-RPM package manager
|
||||
type APTRpm struct {
|
||||
CommonPackageManager
|
||||
CommonRPM
|
||||
}
|
||||
|
||||
func NewAPTRpm() *APTRpm {
|
||||
return &APTRpm{
|
||||
CommonPackageManager: CommonPackageManager{
|
||||
noConfirmArg: "-y",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (*APTRpm) Name() string {
|
||||
return "apt-rpm"
|
||||
}
|
||||
|
||||
func (*APTRpm) Format() string {
|
||||
return "rpm"
|
||||
}
|
||||
|
||||
func (*APTRpm) Exists() bool {
|
||||
cmd := exec.Command("apt-config", "dump")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.Contains(string(output), "RPM")
|
||||
}
|
||||
|
||||
func (a *APTRpm) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apt-get", "update")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("apt-get: sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APTRpm) Install(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apt-get", "install", "-o", "APT::Install::Virtual=true")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
cmd.Stdout = cmd.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("apt-get: install: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APTRpm) InstallLocal(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
return a.Install(opts, pkgs...)
|
||||
}
|
||||
|
||||
func (a *APTRpm) Remove(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apt-get", "remove")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("apt-get: remove: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APTRpm) Upgrade(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
return a.Install(opts, pkgs...)
|
||||
}
|
||||
|
||||
func (a *APTRpm) UpgradeAll(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apt-get", "dist-upgrade")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("apt-get: upgradeall: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
35
internal/manager/common.go
Normal file
35
internal/manager/common.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/>.
|
||||
|
||||
package manager
|
||||
|
||||
import "os/exec"
|
||||
|
||||
type CommonPackageManager struct {
|
||||
noConfirmArg string
|
||||
}
|
||||
|
||||
func (m *CommonPackageManager) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
||||
cmd := exec.Command(mgrCmd)
|
||||
cmd.Args = append(cmd.Args, opts.Args...)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
|
||||
if opts.NoConfirm {
|
||||
cmd.Args = append(cmd.Args, m.noConfirmArg)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
72
internal/manager/common_rpm.go
Normal file
72
internal/manager/common_rpm.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 manager
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CommonRPM struct{}
|
||||
|
||||
func (c *CommonRPM) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
out := map[string]string{}
|
||||
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
for scanner.Scan() {
|
||||
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
version = strings.TrimPrefix(version, "0:")
|
||||
out[name] = version
|
||||
}
|
||||
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *CommonRPM) IsInstalled(pkg string) (bool, error) {
|
||||
cmd := exec.Command("rpm", "-q", "--whatprovides", pkg)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
if exitErr.ExitCode() == 1 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("rpm: isinstalled: %w, output: %s", err, output)
|
||||
}
|
||||
return true, nil
|
||||
}
|
120
internal/manager/dnf.go
Normal file
120
internal/manager/dnf.go
Normal file
@ -0,0 +1,120 @@
|
||||
// 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 manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type DNF struct {
|
||||
CommonPackageManager
|
||||
CommonRPM
|
||||
}
|
||||
|
||||
func NewDNF() *DNF {
|
||||
return &DNF{
|
||||
CommonPackageManager: CommonPackageManager{
|
||||
noConfirmArg: "-y",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (*DNF) Exists() bool {
|
||||
_, err := exec.LookPath("dnf")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (*DNF) Name() string {
|
||||
return "dnf"
|
||||
}
|
||||
|
||||
func (*DNF) Format() string {
|
||||
return "rpm"
|
||||
}
|
||||
|
||||
// Sync выполняет upgrade всех установленных пакетов, обновляя их до более новых версий
|
||||
func (d *DNF) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts) // Гарантирует, что opts не равен nil и содержит допустимые значения
|
||||
cmd := d.getCmd(opts, "dnf", "upgrade")
|
||||
setCmdEnv(cmd) // Устанавливает переменные окружения для команды
|
||||
err := cmd.Run() // Выполняет команду
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnf: sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Install устанавливает указанные пакеты с помощью DNF
|
||||
func (d *DNF) Install(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := d.getCmd(opts, "dnf", "install", "--allowerasing")
|
||||
cmd.Args = append(cmd.Args, pkgs...) // Добавляем названия пакетов к команде
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnf: install: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InstallLocal расширяет метод Install для установки пакетов, расположенных локально
|
||||
func (d *DNF) InstallLocal(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
return d.Install(opts, pkgs...)
|
||||
}
|
||||
|
||||
// Remove удаляет указанные пакеты с помощью DNF
|
||||
func (d *DNF) Remove(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := d.getCmd(opts, "dnf", "remove")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnf: remove: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upgrade обновляет указанные пакеты до более новых версий
|
||||
func (d *DNF) Upgrade(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := d.getCmd(opts, "dnf", "upgrade")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnf: upgrade: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpgradeAll обновляет все установленные пакеты
|
||||
func (d *DNF) UpgradeAll(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := d.getCmd(opts, "dnf", "upgrade")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnf: upgradeall: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,20 +1,21 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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 manager
|
||||
|
||||
@ -26,27 +27,22 @@ import (
|
||||
var Args []string
|
||||
|
||||
type Opts struct {
|
||||
AsRoot bool
|
||||
NoConfirm bool
|
||||
Args []string
|
||||
}
|
||||
|
||||
var DefaultOpts = &Opts{
|
||||
AsRoot: true,
|
||||
NoConfirm: false,
|
||||
}
|
||||
|
||||
// DefaultRootCmd is the command used for privilege elevation by default
|
||||
var DefaultRootCmd = "sudo"
|
||||
|
||||
var managers = []Manager{
|
||||
&Pacman{},
|
||||
&APT{},
|
||||
&DNF{},
|
||||
&YUM{},
|
||||
&APK{},
|
||||
&Zypper{},
|
||||
&APTRpm{},
|
||||
NewPacman(),
|
||||
NewAPT(),
|
||||
NewDNF(),
|
||||
NewYUM(),
|
||||
NewAPK(),
|
||||
NewZypper(),
|
||||
NewAPTRpm(),
|
||||
}
|
||||
|
||||
// Register registers a new package manager
|
||||
@ -63,8 +59,7 @@ type Manager interface {
|
||||
Format() string
|
||||
// Returns true if the package manager exists on the system.
|
||||
Exists() bool
|
||||
// Sets the command used to elevate privileges. Defaults to DefaultRootCmd.
|
||||
SetRootCmd(string)
|
||||
|
||||
// Sync fetches repositories without installing anything
|
||||
Sync(*Opts) error
|
||||
// Install installs packages
|
||||
@ -79,6 +74,8 @@ type Manager interface {
|
||||
UpgradeAll(*Opts) error
|
||||
// ListInstalled returns all installed packages mapped to their versions
|
||||
ListInstalled(*Opts) (map[string]string, error)
|
||||
//
|
||||
IsInstalled(string) (bool, error)
|
||||
}
|
||||
|
||||
// Detect returns the package manager detected on the system
|
||||
@ -101,18 +98,10 @@ func Get(name string) Manager {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRootCmd returns rootCmd if it's not empty, otherwise returns DefaultRootCmd
|
||||
func getRootCmd(rootCmd string) string {
|
||||
if rootCmd != "" {
|
||||
return rootCmd
|
||||
}
|
||||
return DefaultRootCmd
|
||||
}
|
||||
|
||||
func setCmdEnv(cmd *exec.Cmd) {
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
// 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 manager
|
||||
|
||||
@ -27,7 +28,15 @@ import (
|
||||
|
||||
// Pacman represents the Pacman package manager
|
||||
type Pacman struct {
|
||||
rootCmd string
|
||||
CommonPackageManager
|
||||
}
|
||||
|
||||
func NewPacman() *Pacman {
|
||||
return &Pacman{
|
||||
CommonPackageManager: CommonPackageManager{
|
||||
noConfirmArg: "--noconfirm",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (*Pacman) Exists() bool {
|
||||
@ -43,10 +52,6 @@ func (*Pacman) Format() string {
|
||||
return "archlinux"
|
||||
}
|
||||
|
||||
func (p *Pacman) SetRootCmd(s string) {
|
||||
p.rootCmd = s
|
||||
}
|
||||
|
||||
func (p *Pacman) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := p.getCmd(opts, "pacman", "-Sy")
|
||||
@ -141,19 +146,17 @@ func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(p.rootCmd), mgrCmd)
|
||||
cmd.Args = append(cmd.Args, opts.Args...)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
} else {
|
||||
cmd = exec.Command(mgrCmd, args...)
|
||||
func (p *Pacman) IsInstalled(pkg string) (bool, error) {
|
||||
cmd := exec.Command("pacman", "-Q", pkg)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
// Pacman returns exit code 1 if the package is not found
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
if exitErr.ExitCode() == 1 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("pacman: isinstalled: %w, output: %s", err, output)
|
||||
}
|
||||
|
||||
if opts.NoConfirm {
|
||||
cmd.Args = append(cmd.Args, "--noconfirm")
|
||||
}
|
||||
|
||||
return cmd
|
||||
return true, nil
|
||||
}
|
115
internal/manager/yum.go
Normal file
115
internal/manager/yum.go
Normal file
@ -0,0 +1,115 @@
|
||||
// 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 manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// YUM represents the YUM package manager
|
||||
type YUM struct {
|
||||
CommonPackageManager
|
||||
CommonRPM
|
||||
}
|
||||
|
||||
func NewYUM() *YUM {
|
||||
return &YUM{
|
||||
CommonPackageManager: CommonPackageManager{
|
||||
noConfirmArg: "-y",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (*YUM) Exists() bool {
|
||||
_, err := exec.LookPath("yum")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (*YUM) Name() string {
|
||||
return "yum"
|
||||
}
|
||||
|
||||
func (*YUM) Format() string {
|
||||
return "rpm"
|
||||
}
|
||||
|
||||
func (y *YUM) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := y.getCmd(opts, "yum", "upgrade")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("yum: sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (y *YUM) Install(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := y.getCmd(opts, "yum", "install", "--allowerasing")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("yum: install: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (y *YUM) InstallLocal(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
return y.Install(opts, pkgs...)
|
||||
}
|
||||
|
||||
func (y *YUM) Remove(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := y.getCmd(opts, "yum", "remove")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("yum: remove: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (y *YUM) Upgrade(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := y.getCmd(opts, "yum", "upgrade")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("yum: upgrade: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (y *YUM) UpgradeAll(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := y.getCmd(opts, "yum", "upgrade")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("yum: upgradeall: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user