Compare commits
1 Commits
v0.0.18
...
c36d46cf96
Author | SHA1 | Date | |
---|---|---|---|
c36d46cf96 |
@@ -19,7 +19,7 @@ name: Pre-commit
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
|
||||||
|
@@ -47,7 +47,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Prepare for install
|
- name: Prepare for install
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update && apt-get install -y libcap2-bin bindfs
|
||||||
|
|
||||||
- name: Build alr
|
- name: Build alr
|
||||||
env:
|
env:
|
||||||
@@ -84,32 +84,37 @@ jobs:
|
|||||||
sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh
|
sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh
|
||||||
sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh
|
sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh
|
||||||
|
|
||||||
- name: Install alr
|
# - name: Install alr
|
||||||
env:
|
# run: |
|
||||||
CREATE_SYSTEM_RESOURCES: 0
|
# make install
|
||||||
run: |
|
#
|
||||||
make install
|
# # temporary fix
|
||||||
|
# groupadd wheel
|
||||||
|
# usermod -aG wheel root
|
||||||
|
|
||||||
- name: Prepare directories for ALR
|
# - name: Build packages
|
||||||
run: |
|
# run: |
|
||||||
# Создаём необходимые директории для работы alr build
|
# SCRIPT_PATH=alr-default/alr-bin/alr.sh
|
||||||
mkdir -p /tmp/alr/dl /tmp/alr/pkgs /var/cache/alr
|
# ALR_DISTRO=altlinux ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
||||||
chmod -R 777 /tmp/alr
|
# ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
||||||
chmod -R 755 /var/cache/alr
|
# ALR_PKG_FORMAT=deb alr build -s "$SCRIPT_PATH"
|
||||||
|
# ALR_PKG_FORMAT=archlinux alr build -s "$SCRIPT_PATH"
|
||||||
|
|
||||||
- name: Build packages
|
# - name: Upload assets
|
||||||
run: |
|
# uses: akkuman/gitea-release-action@v1
|
||||||
SCRIPT_PATH=alr-default/alr-bin/alr.sh
|
# with:
|
||||||
ALR_DISTRO=altlinux ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
# body: ${{ steps.changes.outputs.changes }}
|
||||||
ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
# files: |-
|
||||||
ALR_PKG_FORMAT=deb alr build -s "$SCRIPT_PATH"
|
# alr-bin+alr-default_${{ env.VERSION }}-1.red80_amd64.deb \
|
||||||
ALR_PKG_FORMAT=archlinux alr build -s "$SCRIPT_PATH"
|
# 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: Upload assets
|
- name: Commit changes
|
||||||
uses: akkuman/gitea-release-action@v1
|
run: |
|
||||||
with:
|
cd alr-default
|
||||||
body: ${{ steps.changes.outputs.changes }}
|
git config user.name "gitea"
|
||||||
files: |-
|
git config user.email "admin@plemya-x.ru"
|
||||||
alr-bin*.deb
|
git add .
|
||||||
alr-bin*.rpm
|
git commit -m "Обновление версии до ${{ env.VERSION }}"
|
||||||
alr-bin*.pkg.tar.zst
|
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
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -3,12 +3,11 @@
|
|||||||
/cmd/alr-api-server/alr-api-server
|
/cmd/alr-api-server/alr-api-server
|
||||||
/dist/
|
/dist/
|
||||||
/internal/config/version.txt
|
/internal/config/version.txt
|
||||||
.fleet/
|
.fleet
|
||||||
.idea/
|
.idea
|
||||||
.gigaide/
|
.gigaide
|
||||||
|
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
e2e-tests/alr
|
e2e-tests/alr
|
||||||
CLAUDE.md
|
|
||||||
commit_msg.txt
|
commit_msg.txt
|
@@ -36,14 +36,11 @@ linters:
|
|||||||
- unused
|
- unused
|
||||||
- errcheck
|
- errcheck
|
||||||
- typecheck
|
- typecheck
|
||||||
- wrapcheck
|
# - forbidigo
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
fix: true
|
fix: true
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
- linters:
|
|
||||||
- wrapcheck
|
|
||||||
path-except: "internal/repos/find.go"
|
|
||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
linters:
|
linters:
|
||||||
- errcheck
|
- errcheck
|
||||||
|
@@ -19,13 +19,13 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: test-coverage
|
- id: test-coverage
|
||||||
name: Run test coverage
|
name: Run test coverage
|
||||||
entry: bash scripts/test-coverage-precommit.sh
|
entry: make test-coverage
|
||||||
language: system
|
language: system
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
|
|
||||||
- id: fmt
|
- id: fmt
|
||||||
name: Format code
|
name: Format code
|
||||||
entry: bash scripts/fmt-precommit.sh
|
entry: make fmt
|
||||||
language: system
|
language: system
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
|
|
||||||
@@ -37,7 +37,6 @@ repos:
|
|||||||
|
|
||||||
- id: i18n
|
- id: i18n
|
||||||
name: Update i18n
|
name: Update i18n
|
||||||
entry: bash scripts/i18n-precommit.sh
|
entry: make i18n
|
||||||
language: system
|
language: system
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
always_run: true
|
|
36
Makefile
36
Makefile
@@ -1,21 +1,16 @@
|
|||||||
NAME := alr
|
NAME := alr
|
||||||
GIT_VERSION ?= $(shell git describe --tags )
|
GIT_VERSION = $(shell git describe --tags )
|
||||||
IGNORE_ROOT_CHECK ?= 0
|
IGNORE_ROOT_CHECK ?= 0
|
||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
BIN := ./$(NAME)
|
BIN := ./$(NAME)
|
||||||
INSTALLED_BIN := $(DESTDIR)/$(PREFIX)/bin/$(NAME)
|
INSTALED_BIN := $(DESTDIR)/$(PREFIX)/bin/$(NAME)
|
||||||
COMPLETIONS_DIR := ./scripts/completion
|
COMPLETIONS_DIR := ./scripts/completion
|
||||||
BASH_COMPLETION := $(COMPLETIONS_DIR)/bash
|
BASH_COMPLETION := $(COMPLETIONS_DIR)/bash
|
||||||
ZSH_COMPLETION := $(COMPLETIONS_DIR)/zsh
|
ZSH_COMPLETION := $(COMPLETIONS_DIR)/zsh
|
||||||
INSTALLED_BASH_COMPLETION := $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(NAME)
|
INSTALLED_BASH_COMPLETION := $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(NAME)
|
||||||
INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME)
|
INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME)
|
||||||
|
|
||||||
GENERATE ?= 1
|
|
||||||
|
|
||||||
CREATE_SYSTEM_RESOURCES ?= 1
|
|
||||||
ROOT_DIRS := /var/cache/alr /etc/alr
|
|
||||||
|
|
||||||
ADD_LICENSE_BIN := go run github.com/google/addlicense@4caba19b7ed7818bb86bc4cd20411a246aa4a524
|
ADD_LICENSE_BIN := go run github.com/google/addlicense@4caba19b7ed7818bb86bc4cd20411a246aa4a524
|
||||||
GOLANGCI_LINT_BIN := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4
|
GOLANGCI_LINT_BIN := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4
|
||||||
XGOTEXT_BIN := go run github.com/Tom5521/xgotext@v1.2.0
|
XGOTEXT_BIN := go run github.com/Tom5521/xgotext@v1.2.0
|
||||||
@@ -26,11 +21,7 @@ build: check-no-root $(BIN)
|
|||||||
|
|
||||||
export CGO_ENABLED := 0
|
export CGO_ENABLED := 0
|
||||||
$(BIN):
|
$(BIN):
|
||||||
ifeq ($(GENERATE),1)
|
|
||||||
go generate ./...
|
go generate ./...
|
||||||
else
|
|
||||||
@echo "Skipping go generate (GENERATE=0)"
|
|
||||||
endif
|
|
||||||
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
|
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
|
||||||
|
|
||||||
check-no-root:
|
check-no-root:
|
||||||
@@ -41,21 +32,20 @@ check-no-root:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
install: \
|
install: \
|
||||||
$(INSTALLED_BIN) \
|
$(INSTALED_BIN) \
|
||||||
$(INSTALLED_BASH_COMPLETION) \
|
$(INSTALLED_BASH_COMPLETION) \
|
||||||
$(INSTALLED_ZSH_COMPLETION)
|
$(INSTALLED_ZSH_COMPLETION)
|
||||||
@echo "Installation done!"
|
@echo "Installation done!"
|
||||||
|
|
||||||
$(INSTALLED_BIN): $(BIN)
|
$(INSTALED_BIN): $(BIN)
|
||||||
install -Dm755 $< $@
|
install -Dm755 $< $@
|
||||||
ifeq ($(CREATE_SYSTEM_RESOURCES),1)
|
setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN)
|
||||||
@for dir in $(ROOT_DIRS); do \
|
@if id alr >/dev/null 2>&1; then \
|
||||||
install -d -m 775 $$dir; \
|
echo "User 'alr' already exists. Skipping."; \
|
||||||
chgrp wheel $$dir; \
|
else \
|
||||||
done
|
useradd -r -s /usr/sbin/nologin alr; \
|
||||||
else
|
fi
|
||||||
@echo "Skipping root dir creation (CREATE_SYSTEM_RESOURCES=0)"
|
install -d -o alr -g alr -m 755 /var/cache/alr /etc/alr
|
||||||
endif
|
|
||||||
|
|
||||||
$(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION)
|
$(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION)
|
||||||
install -Dm755 $< $@
|
install -Dm755 $< $@
|
||||||
@@ -65,7 +55,7 @@ $(INSTALLED_ZSH_COMPLETION): $(ZSH_COMPLETION)
|
|||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f \
|
rm -f \
|
||||||
$(INSTALLED_BIN) \
|
$(INSTALED_BIN) \
|
||||||
$(INSTALLED_BASH_COMPLETION) \
|
$(INSTALLED_BASH_COMPLETION) \
|
||||||
$(INSTALLED_ZSH_COMPLETION)
|
$(INSTALLED_ZSH_COMPLETION)
|
||||||
|
|
||||||
@@ -88,7 +78,7 @@ i18n:
|
|||||||
bash scripts/i18n-badge.sh
|
bash scripts/i18n-badge.sh
|
||||||
|
|
||||||
test-coverage:
|
test-coverage:
|
||||||
go test -tags=test ./... -v -coverpkg=./... -coverprofile=coverage.out
|
go test ./... -v -coverpkg=./... -coverprofile=coverage.out
|
||||||
bash scripts/coverage-badge.sh
|
bash scripts/coverage-badge.sh
|
||||||
|
|
||||||
update-deps-cve:
|
update-deps-cve:
|
||||||
|
12
README.md
12
README.md
@@ -44,7 +44,7 @@ ALR был создан потому, что упаковка программн
|
|||||||
|
|
||||||
## Документация
|
## Документация
|
||||||
|
|
||||||
Документация находится в [Wiki](https://alr.plemya-x.ru/wiki/ALR).
|
Документация находится в [Wiki](https://disc.plemya-x.ru/c/alr/wiki-alr).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -52,21 +52,23 @@ ALR был создан потому, что упаковка программн
|
|||||||
|
|
||||||
Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.
|
Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.
|
||||||
|
|
||||||
Например, репозиторий с ALR [alr-default](https://gitea.plemya-x.ru/Plemya-x/alr-default.git)
|
Например, репозиторий с 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
|
alr repo add alr-default https://gitea.plemya-x.ru/Plemya-x/alr-default.git
|
||||||
```
|
```
|
||||||
Репозиторий пакетов [alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так:
|
Репозиторий пакетов [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
|
alr repo add alr-repo https://gitea.plemya-x.ru/Plemya-x/alr-repo.git
|
||||||
```
|
```
|
||||||
Репозиторий Linux-Gaming [alr-LG](https://gitea.plemya-x.ru/Plemya-x/alr-LG.git) можно подключить так:
|
Репозиторий Linux-Gaming [Plemya-x/alr-LG](https://gitea.plemya-x.ru/Plemya-x/alr-LG.git) можно подключить так:
|
||||||
```
|
```
|
||||||
alr repo add alr-LG https://git.linux-gaming.ru/Linux-Gaming/alr-LG.git
|
alr repo add alr-LG https://gitea.plemya-x.ru/Plemya-x/alr-LG.git
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
## Соцсети
|
## Соцсети
|
||||||
|
VK - https://vk.com/plemya_kh
|
||||||
|
|
||||||
Telegram - https://t.me/plemyakh
|
Telegram - https://t.me/plemyakh
|
||||||
|
|
||||||
## Спасибы
|
## Спасибы
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||||
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
||||||
<text x="33.5" y="14">coverage</text>
|
<text x="33.5" y="14">coverage</text>
|
||||||
<text x="86" y="15" fill="#010101" fill-opacity=".3">18.9%</text>
|
<text x="86" y="15" fill="#010101" fill-opacity=".3">19.3%</text>
|
||||||
<text x="86" y="14">18.9%</text>
|
<text x="86" y="14">19.3%</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B |
35
build.go
35
build.go
@@ -23,6 +23,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@@ -72,6 +73,12 @@ func BuildCmd() *cli.Command {
|
|||||||
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
|
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wd, wdCleanup, err := Mount(wd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer wdCleanup()
|
||||||
|
|
||||||
ctx := c.Context
|
ctx := c.Context
|
||||||
|
|
||||||
deps, err := appbuilder.
|
deps, err := appbuilder.
|
||||||
@@ -126,7 +133,15 @@ func BuildCmd() *cli.Command {
|
|||||||
// TODO: handle multiple packages
|
// TODO: handle multiple packages
|
||||||
packageInput := c.String("package")
|
packageInput := c.String("package")
|
||||||
|
|
||||||
pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageInput})
|
arr := strings.Split(packageInput, "/")
|
||||||
|
var packageSearch string
|
||||||
|
if len(arr) == 2 {
|
||||||
|
packageSearch = arr[1]
|
||||||
|
} else {
|
||||||
|
packageSearch = arr[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageSearch})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliutils.FormatCliExit("failed to find pkgs", err)
|
return cliutils.FormatCliExit("failed to find pkgs", err)
|
||||||
}
|
}
|
||||||
@@ -150,9 +165,19 @@ func BuildCmd() *cli.Command {
|
|||||||
return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil)
|
return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if scriptArgs != nil {
|
||||||
|
scriptFile := filepath.Base(scriptArgs.Script)
|
||||||
|
newScriptDir, scriptDirCleanup, err := Mount(filepath.Dir(scriptArgs.Script))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer scriptDirCleanup()
|
||||||
|
scriptArgs.Script = filepath.Join(newScriptDir, scriptFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
installer, installerClose, err := build.GetSafeInstaller()
|
installer, installerClose, err := build.GetSafeInstaller()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -160,7 +185,9 @@ func BuildCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
defer installerClose()
|
defer installerClose()
|
||||||
|
|
||||||
|
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
scripter, scripterClose, err := build.GetSafeScriptExecutor()
|
scripter, scripterClose, err := build.GetSafeScriptExecutor()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -76,7 +76,6 @@ var configKeys = []string{
|
|||||||
"autoPull",
|
"autoPull",
|
||||||
"logLevel",
|
"logLevel",
|
||||||
"ignorePkgUpdates",
|
"ignorePkgUpdates",
|
||||||
"updateSystemOnUpgrade",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetConfig() *cli.Command {
|
func SetConfig() *cli.Command {
|
||||||
@@ -138,12 +137,6 @@ func SetConfig() *cli.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
deps.Cfg.System.SetIgnorePkgUpdates(updates)
|
deps.Cfg.System.SetIgnorePkgUpdates(updates)
|
||||||
case "updateSystemOnUpgrade":
|
|
||||||
boolValue, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return cliutils.FormatCliExit(gotext.Get("invalid boolean value for %s: %s", key, value), err)
|
|
||||||
}
|
|
||||||
deps.Cfg.System.SetUpdateSystemOnUpgrade(boolValue)
|
|
||||||
case "repo", "repos":
|
case "repo", "repos":
|
||||||
return cliutils.FormatCliExit(gotext.Get("use 'repo add/remove' commands to manage repositories"), nil)
|
return cliutils.FormatCliExit(gotext.Get("use 'repo add/remove' commands to manage repositories"), nil)
|
||||||
default:
|
default:
|
||||||
@@ -213,8 +206,6 @@ func GetConfig() *cli.Command {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Println(strings.Join(updates, ", "))
|
fmt.Println(strings.Join(updates, ", "))
|
||||||
}
|
}
|
||||||
case "updateSystemOnUpgrade":
|
|
||||||
fmt.Println(deps.Cfg.UpdateSystemOnUpgrade())
|
|
||||||
case "repo", "repos":
|
case "repo", "repos":
|
||||||
repos := deps.Cfg.Repos()
|
repos := deps.Cfg.Repos()
|
||||||
if len(repos) == 0 {
|
if len(repos) == 0 {
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
- name: alr-repo
|
|
||||||
url: https://gitea.plemya-x.ru/Plemya-x/repo-for-tests
|
|
||||||
ref: main
|
|
||||||
mirrors:
|
|
||||||
- https://github.com/example/example.git
|
|
@@ -1 +0,0 @@
|
|||||||
alr-repo/foo-pkg 1.0.0-1
|
|
@@ -1,2 +0,0 @@
|
|||||||
alr-repo/bar-pkg 1.0.0-1
|
|
||||||
alr-repo/foo-pkg 1.0.0-1
|
|
@@ -19,24 +19,54 @@
|
|||||||
package e2etests_test
|
package e2etests_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EAlrAddRepo(t *testing.T) {
|
func TestE2EAlrAddRepo(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"add-repo-remove-repo",
|
"add-repo-remove-repo",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
execShouldNoError(t, r, "sudo", "alr", "addrepo", "--name", "alr-repo", "--url", "https://gitea.plemya-x.ru/Plemya-x/alr-repo.git")
|
err := r.Exec(e2e.NewCommand(
|
||||||
execShouldNoError(t, r, "bash", "-c", "cat /etc/alr/alr.toml")
|
"sudo",
|
||||||
execShouldNoError(t, r, "sudo", "alr", "removerepo", "--name", "alr-repo")
|
"alr",
|
||||||
|
"addrepo",
|
||||||
|
"--name",
|
||||||
|
"alr-repo",
|
||||||
|
"--url",
|
||||||
|
"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git",
|
||||||
|
))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
r.Command("bash", "-c", "cat /etc/alr/alr.toml").
|
err = r.Exec(e2e.NewCommand(
|
||||||
ExpectStdoutContains("repo = []").
|
"bash",
|
||||||
Run(t)
|
"-c",
|
||||||
|
"cat /etc/alr/alr.toml",
|
||||||
|
))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = r.Exec(e2e.NewCommand(
|
||||||
|
"sudo",
|
||||||
|
"alr",
|
||||||
|
"removerepo",
|
||||||
|
"--name",
|
||||||
|
"alr-repo",
|
||||||
|
))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = r.Exec(e2e.NewCommand(
|
||||||
|
"bash",
|
||||||
|
"-c",
|
||||||
|
"cat /etc/alr/alr.toml",
|
||||||
|
), e2e.WithExecOptionStdout(&buf))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, buf.String(), "repo = []")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EBashCompletion(t *testing.T) {
|
func TestE2EBashCompletion(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"bash-completion",
|
"bash-completion",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
execShouldNoError(t, r, "alr", "install", "--generate-bash-completion")
|
execShouldNoError(t, r, "alr", "install", "--generate-bash-completion")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@@ -19,13 +19,84 @@
|
|||||||
package e2etests_test
|
package e2etests_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
"go.alt-gnome.ru/capytest/providers/podman"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
expect "github.com/tailscale/goexpect"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DebugWriter оборачивает io.Writer и логирует все записываемые данные.
|
||||||
|
type DebugWriter struct {
|
||||||
|
prefix string
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DebugWriter) Write(p []byte) (n int, err error) {
|
||||||
|
log.Printf("%s: Writing data: %q", d.prefix, p) // Логируем данные
|
||||||
|
return d.writer.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugReader оборачивает io.Reader и логирует все читаемые данные.
|
||||||
|
type DebugReader struct {
|
||||||
|
prefix string
|
||||||
|
reader io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DebugReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = d.reader.Read(p)
|
||||||
|
if n > 0 {
|
||||||
|
log.Printf("%s: Read data: %q", d.prefix, p[:n]) // Логируем данные
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func e2eSpawn(runnable e2e.Runnable, command e2e.Command, timeout time.Duration, opts ...expect.Option) (expect.Expecter, <-chan error, error, *io.PipeWriter) {
|
||||||
|
resCh := make(chan error)
|
||||||
|
|
||||||
|
// Создаем pipe для stdin и stdout
|
||||||
|
stdinReader, stdinWriter := io.Pipe()
|
||||||
|
stdoutReader, stdoutWriter := io.Pipe()
|
||||||
|
|
||||||
|
debugStdinReader := &DebugReader{prefix: "STDIN", reader: stdinReader}
|
||||||
|
debugStdoutWriter := &DebugWriter{prefix: "STDOUT", writer: stdoutWriter}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := runnable.Exec(
|
||||||
|
command,
|
||||||
|
e2e.WithExecOptionStdout(debugStdoutWriter),
|
||||||
|
e2e.WithExecOptionStdin(debugStdinReader),
|
||||||
|
e2e.WithExecOptionStderr(debugStdoutWriter),
|
||||||
|
)
|
||||||
|
|
||||||
|
resCh <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
exp, chnErr, err := expect.SpawnGeneric(&expect.GenOptions{
|
||||||
|
In: stdinWriter,
|
||||||
|
Out: stdoutReader,
|
||||||
|
Wait: func() error {
|
||||||
|
return <-resCh
|
||||||
|
},
|
||||||
|
Close: func() error {
|
||||||
|
stdinWriter.Close()
|
||||||
|
stdoutReader.Close()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Check: func() bool { return true },
|
||||||
|
}, timeout, expect.Verbose(true), expect.VerboseWriter(os.Stdout))
|
||||||
|
|
||||||
|
return exp, chnErr, err, stdinWriter
|
||||||
|
}
|
||||||
|
|
||||||
var ALL_SYSTEMS []string = []string{
|
var ALL_SYSTEMS []string = []string{
|
||||||
"ubuntu-24.04",
|
"ubuntu-24.04",
|
||||||
"alt-sisyphus",
|
"alt-sisyphus",
|
||||||
@@ -49,20 +120,71 @@ var COMMON_SYSTEMS []string = []string{
|
|||||||
"ubuntu-24.04",
|
"ubuntu-24.04",
|
||||||
}
|
}
|
||||||
|
|
||||||
func execShouldNoError(t *testing.T, r capytest.Runner, cmd string, args ...string) {
|
func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testing.T, runnable e2e.Runnable)) {
|
||||||
t.Helper()
|
t.Run(name, func(t *testing.T) {
|
||||||
r.Command(cmd, args...).ExpectSuccess().Run(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 execShouldError(t *testing.T, r capytest.Runner, cmd string, args ...string) {
|
func execShouldNoError(t *testing.T, r e2e.Runnable, cmd string, args ...string) {
|
||||||
t.Helper()
|
assert.NoError(t, r.Exec(e2e.NewCommand(cmd, args...)))
|
||||||
r.Command(cmd, args...).ExpectFailure().Run(t)
|
}
|
||||||
|
|
||||||
|
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_NAME_FOR_E2E_TESTS = "alr-repo"
|
||||||
const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git"
|
const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git"
|
||||||
|
|
||||||
func defaultPrepare(t *testing.T, r capytest.Runner) {
|
func defaultPrepare(t *testing.T, r e2e.Runnable) {
|
||||||
execShouldNoError(t, r,
|
execShouldNoError(t, r,
|
||||||
"sudo",
|
"sudo",
|
||||||
"alr",
|
"alr",
|
||||||
@@ -78,19 +200,3 @@ func defaultPrepare(t *testing.T, r capytest.Runner) {
|
|||||||
"ref",
|
"ref",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMatrixSuite(t *testing.T, name string, images []string, test func(t *testing.T, r capytest.Runner)) {
|
|
||||||
t.Helper()
|
|
||||||
for _, image := range images {
|
|
||||||
ts := capytest.NewTestSuite(t, podman.Provider(
|
|
||||||
podman.WithImage(fmt.Sprintf("ghcr.io/maks1ms/alr-e2e-test-image-%s", image)),
|
|
||||||
podman.WithVolumes("./alr:/tmp/alr"),
|
|
||||||
podman.WithPrivileged(true),
|
|
||||||
))
|
|
||||||
ts.BeforeEach(func(t *testing.T, r capytest.Runner) {
|
|
||||||
execShouldNoError(t, r, "/bin/alr-test-setup", "alr-install")
|
|
||||||
execShouldNoError(t, r, "/bin/alr-test-setup", "passwordless-sudo-setup")
|
|
||||||
})
|
|
||||||
ts.Run(fmt.Sprintf("%s/%s", name, image), test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -22,15 +22,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EFirejailedPackage(t *testing.T) {
|
func TestE2EFirejailedPackage(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"firejailed-package",
|
"firejailed-package",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldNoError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg", REPO_NAME_FOR_E2E_TESTS))
|
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))
|
execShouldError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg-incorrect", REPO_NAME_FOR_E2E_TESTS))
|
||||||
|
@@ -20,15 +20,24 @@ package e2etests_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
|
expect "github.com/tailscale/goexpect"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EAlrFix(t *testing.T) {
|
func TestE2EAlrFix(t *testing.T) {
|
||||||
runMatrixSuite(t, "run-fix", COMMON_SYSTEMS, func(t *testing.T, r capytest.Runner) {
|
dockerMultipleRun(
|
||||||
r.Command("alr", "fix").
|
t,
|
||||||
ExpectStderrContains("--> Done").
|
"run-fix",
|
||||||
ExpectSuccess().
|
COMMON_SYSTEMS,
|
||||||
Run(t)
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
})
|
runTestCommands(t, r, time.Second*30, []expect.Batcher{
|
||||||
|
&expect.BSnd{S: "alr fix\n"},
|
||||||
|
&expect.BExp{R: `--> Done`},
|
||||||
|
&expect.BSnd{S: "echo $?\n"},
|
||||||
|
&expect.BExp{R: `^0\n$`},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EGroupAndSummaryField(t *testing.T) {
|
func TestE2EGroupAndSummaryField(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"group-and-summary-field",
|
"group-and-summary-field",
|
||||||
RPM_SYSTEMS,
|
RPM_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
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 \"{{.Group.Resolved}}\" | grep ^System/Base$")
|
||||||
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary.Resolved}}\" | grep \"^Custom summary$\"")
|
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary.Resolved}}\" | grep \"^Custom summary$\"")
|
||||||
|
@@ -1,40 +0,0 @@
|
|||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//go:build e2e
|
|
||||||
|
|
||||||
package e2etests_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestE2EIssue129RepoTomlImportTest(t *testing.T) {
|
|
||||||
runMatrixSuite(
|
|
||||||
t,
|
|
||||||
"issue-129-repo-toml-import-test",
|
|
||||||
COMMON_SYSTEMS,
|
|
||||||
func(t *testing.T, r capytest.Runner) {
|
|
||||||
defaultPrepare(t, r)
|
|
||||||
|
|
||||||
r.Command("alr", "config", "get", "repos").
|
|
||||||
ExpectStdoutMatchesSnapshot().
|
|
||||||
Run(t)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
@@ -1,63 +0,0 @@
|
|||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//go:build e2e
|
|
||||||
|
|
||||||
package e2etests_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestE2EIssue130Install(t *testing.T) {
|
|
||||||
runMatrixSuite(
|
|
||||||
t,
|
|
||||||
"alr install {repo}/{package}",
|
|
||||||
COMMON_SYSTEMS,
|
|
||||||
func(t *testing.T, r capytest.Runner) {
|
|
||||||
t.Parallel()
|
|
||||||
defaultPrepare(t, r)
|
|
||||||
|
|
||||||
r.Command("sudo", "alr", "in", fmt.Sprintf("%s/foo-pkg", REPO_NAME_FOR_E2E_TESTS)).
|
|
||||||
ExpectSuccess().
|
|
||||||
Run(t)
|
|
||||||
|
|
||||||
r.Command("sudo", "alr", "in", fmt.Sprintf("%s/bar-pkg", "NOT_REPO_NAME_FOR_E2E_TESTS")).
|
|
||||||
ExpectFailure().
|
|
||||||
Run(t)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
runMatrixSuite(
|
|
||||||
t,
|
|
||||||
"alr install {package}+alr-{repo}",
|
|
||||||
COMMON_SYSTEMS,
|
|
||||||
func(t *testing.T, r capytest.Runner) {
|
|
||||||
t.Parallel()
|
|
||||||
defaultPrepare(t, r)
|
|
||||||
|
|
||||||
r.Command("sudo", "alr", "in", fmt.Sprintf("foo-pkg+alr-%s", REPO_NAME_FOR_E2E_TESTS)).
|
|
||||||
ExpectSuccess().
|
|
||||||
Run(t)
|
|
||||||
|
|
||||||
r.Command("sudo", "alr", "in", fmt.Sprintf("bar-pkg+alr-%s", "NOT_REPO_NAME_FOR_E2E_TESTS")).
|
|
||||||
ExpectFailure().
|
|
||||||
Run(t)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue32Interactive(t *testing.T) {
|
func TestE2EIssue32Interactive(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-32-interactive",
|
"issue-32-interactive",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
execShouldNoError(t, r, "alr", "--interactive=false", "remove", "ca-certificates")
|
execShouldNoError(t, r, "alr", "--interactive=false", "remove", "ca-certificates")
|
||||||
execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "remove", "openssl")
|
execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "remove", "openssl")
|
||||||
execShouldNoError(t, r, "alr", "fix")
|
execShouldNoError(t, r, "alr", "fix")
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue41AutoreqSkiplist(t *testing.T) {
|
func TestE2EIssue41AutoreqSkiplist(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-41-autoreq-skiplist",
|
"issue-41-autoreq-skiplist",
|
||||||
AUTOREQ_AUTOPROV_SYSTEMS,
|
AUTOREQ_AUTOPROV_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldNoError(t, r, "alr", "build", "-p", "alr-repo/test-autoreq-autoprov")
|
execShouldNoError(t, r, "alr", "build", "-p", "alr-repo/test-autoreq-autoprov")
|
||||||
execShouldNoError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/sh$\"")
|
execShouldNoError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/sh$\"")
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue50InstallMultiple(t *testing.T) {
|
func TestE2EIssue50InstallMultiple(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-50-install-multiple",
|
"issue-50-install-multiple",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg")
|
execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg")
|
||||||
execShouldNoError(t, r, "cat", "/opt/foo")
|
execShouldNoError(t, r, "cat", "/opt/foo")
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue53LcAllCInfo(t *testing.T) {
|
func TestE2EIssue53LcAllCInfo(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-53-lc-all-c-info",
|
"issue-53-lc-all-c-info",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldNoError(t, r, "bash", "-c", "LANG=C alr info foo-pkg")
|
execShouldNoError(t, r, "bash", "-c", "LANG=C alr info foo-pkg")
|
||||||
},
|
},
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue59RmCompletion(t *testing.T) {
|
func TestE2EIssue59RmCompletion(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-59-rm-completion",
|
"issue-59-rm-completion",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg")
|
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 ^foo-pkg$")
|
||||||
|
@@ -1,50 +0,0 @@
|
|||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
//go:build e2e
|
|
||||||
|
|
||||||
package e2etests_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestE2EIssue62List(t *testing.T) {
|
|
||||||
runMatrixSuite(
|
|
||||||
t,
|
|
||||||
"issue-62-list",
|
|
||||||
COMMON_SYSTEMS,
|
|
||||||
func(t *testing.T, r capytest.Runner) {
|
|
||||||
defaultPrepare(t, r)
|
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
|
|
||||||
execShouldNoError(t, r, "alr", "ref")
|
|
||||||
|
|
||||||
execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg")
|
|
||||||
|
|
||||||
r.Command("alr", "list", "-I").
|
|
||||||
ExpectSuccess().
|
|
||||||
ExpectStdoutMatchesSnapshot().
|
|
||||||
Run(t)
|
|
||||||
|
|
||||||
r.Command("alr", "list").
|
|
||||||
ExpectSuccess().
|
|
||||||
ExpectStdoutMatchesSnapshot().
|
|
||||||
Run(t)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue72InstallWithDeps(t *testing.T) {
|
func TestE2EIssue72InstallWithDeps(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-72-install-with-deps",
|
"issue-72-install-with-deps",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldNoError(t, r, "sudo", "alr", "in", "test-app-with-lib")
|
execShouldNoError(t, r, "sudo", "alr", "in", "test-app-with-lib")
|
||||||
},
|
},
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue74Upgradable(t *testing.T) {
|
func TestE2EIssue74Upgradable(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-74-upgradable",
|
"issue-74-upgradable",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
|
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
|
||||||
execShouldNoError(t, r, "alr", "ref")
|
execShouldNoError(t, r, "alr", "ref")
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue75InstallWithDeps(t *testing.T) {
|
func TestE2EIssue75InstallWithDeps(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-75-ref-specify",
|
"issue-75-ref-specify",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
|
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")
|
execShouldNoError(t, r, "sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1")
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test75SinglePackageRepo(t *testing.T) {
|
func Test75SinglePackageRepo(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-76-single-package-repo",
|
"issue-76-single-package-repo",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
execShouldNoError(t, r,
|
execShouldNoError(t, r,
|
||||||
"sudo",
|
"sudo",
|
||||||
"alr",
|
"alr",
|
||||||
@@ -38,9 +38,8 @@ func Test75SinglePackageRepo(t *testing.T) {
|
|||||||
REPO_NAME_FOR_E2E_TESTS,
|
REPO_NAME_FOR_E2E_TESTS,
|
||||||
"https://gitea.plemya-x.ru/Maks1mS/test-single-package-alr-repo.git",
|
"https://gitea.plemya-x.ru/Maks1mS/test-single-package-alr-repo.git",
|
||||||
)
|
)
|
||||||
execShouldNoError(t, r, "sudo", "alr", "ref")
|
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "1075c918be")
|
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "1075c918be")
|
||||||
execShouldNoError(t, r, "alr", "fix")
|
execShouldNoError(t, r, "alr", "ref")
|
||||||
execShouldNoError(t, r, "sudo", "alr", "in", "test-single-repo")
|
execShouldNoError(t, r, "sudo", "alr", "in", "test-single-repo")
|
||||||
execShouldNoError(t, r, "sh", "-c", "alr list -U")
|
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, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
|
||||||
|
@@ -21,29 +21,33 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue78Mirrors(t *testing.T) {
|
func TestE2EIssue78Mirrors(t *testing.T) {
|
||||||
runMatrixSuite(t, "issue-78-mirrors", COMMON_SYSTEMS, func(t *testing.T, r capytest.Runner) {
|
dockerMultipleRun(
|
||||||
defaultPrepare(t, r)
|
t,
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
"issue-78-mirrors",
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "set-url", REPO_NAME_FOR_E2E_TESTS, "https://example.com")
|
COMMON_SYSTEMS,
|
||||||
execShouldNoError(t, r, "sudo", "alr", "ref")
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "clear", REPO_NAME_FOR_E2E_TESTS)
|
defaultPrepare(t, r)
|
||||||
execShouldError(t, r, "sudo", "alr", "ref")
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "set-url", REPO_NAME_FOR_E2E_TESTS, "https://example.com")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "ref")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "clear", REPO_NAME_FOR_E2E_TESTS)
|
||||||
|
execShouldError(t, r, "sudo", "alr", "ref")
|
||||||
|
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", "--partial", REPO_NAME_FOR_E2E_TESTS, "gitea.plemya-x.ru/Maks1mS")
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", "--partial", REPO_NAME_FOR_E2E_TESTS, "gitea.plemya-x.ru/Maks1mS")
|
||||||
execShouldError(t, r, "sudo", "alr", "ref")
|
execShouldError(t, r, "sudo", "alr", "ref")
|
||||||
|
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
execShouldError(t, r, "sudo", "alr", "ref")
|
execShouldError(t, r, "sudo", "alr", "ref")
|
||||||
|
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
execShouldError(t, r, "sudo", "alr", "ref")
|
execShouldError(t, r, "sudo", "alr", "ref")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue81MultiplePackages(t *testing.T) {
|
func TestE2EIssue81MultiplePackages(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-81-multiple-packages",
|
"issue-81-multiple-packages",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldNoError(t, r, "sudo", "alr", "in", "first-package-with-dashes")
|
execShouldNoError(t, r, "sudo", "alr", "in", "first-package-with-dashes")
|
||||||
execShouldNoError(t, r, "cat", "/opt/first-package")
|
execShouldNoError(t, r, "cat", "/opt/first-package")
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue91MultiplePackages(t *testing.T) {
|
func TestE2EIssue91MultiplePackages(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-91-set-repo-ref",
|
"issue-91-set-repo-ref",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldError(t, r, "sudo", "alr", "repo", "set-ref")
|
execShouldError(t, r, "sudo", "alr", "repo", "set-ref")
|
||||||
execShouldError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo")
|
execShouldError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo")
|
||||||
|
@@ -23,26 +23,27 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/efficientgo/e2e"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"go.alt-gnome.ru/capytest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue94TwiceBuild(t *testing.T) {
|
func TestE2EIssue94TwiceBuild(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-94-twice-build",
|
"issue-94-twice-build",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
|
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
|
err := r.Exec(
|
||||||
|
e2e.NewCommand("sudo", "alr", "in", "test-94-app"),
|
||||||
|
e2e.WithExecOptionStderr(&stderr),
|
||||||
|
)
|
||||||
|
assert.NoError(t, err, "command failed")
|
||||||
|
|
||||||
r.Command("sudo", "alr", "in", "test-94-app").
|
output := stderr.String()
|
||||||
WithCaptureStderr(&stderr).
|
assert.Equal(t, 1, strings.Count(output, "Building package name=test-94-dep"))
|
||||||
ExpectSuccess().
|
|
||||||
Run(t)
|
|
||||||
|
|
||||||
assert.Equal(t, 1, strings.Count(stderr.String(), "Building package name=test-94-dep"))
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -21,15 +21,15 @@ package e2etests_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EIssue95ConfigCommand(t *testing.T) {
|
func TestE2EIssue95ConfigCommand(t *testing.T) {
|
||||||
runMatrixSuite(
|
dockerMultipleRun(
|
||||||
t,
|
t,
|
||||||
"issue-95-config-command",
|
"issue-95-config-command",
|
||||||
COMMON_SYSTEMS,
|
COMMON_SYSTEMS,
|
||||||
func(t *testing.T, r capytest.Runner) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldNoError(t, r, "sh", "-c", "alr config show | grep \"autoPull: true\"")
|
execShouldNoError(t, r, "sh", "-c", "alr config show | grep \"autoPull: true\"")
|
||||||
execShouldNoError(t, r, "sh", "-c", "alr config get | grep \"autoPull: true\"")
|
execShouldNoError(t, r, "sh", "-c", "alr config get | grep \"autoPull: true\"")
|
||||||
|
@@ -20,16 +20,25 @@ package e2etests_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.alt-gnome.ru/capytest"
|
"github.com/efficientgo/e2e"
|
||||||
|
|
||||||
|
expect "github.com/tailscale/goexpect"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestE2EAlrVersion(t *testing.T) {
|
func TestE2EAlrVersion(t *testing.T) {
|
||||||
runMatrixSuite(t, "version", COMMON_SYSTEMS, func(t *testing.T, r capytest.Runner) {
|
dockerMultipleRun(
|
||||||
r.Command("alr", "version").
|
t,
|
||||||
ExpectStderrRegex(`^v\d+\.\d+\.\d+(?:-\d+-g[a-f0-9]+)?\n$`).
|
"check-version",
|
||||||
ExpectStdoutEmpty().
|
COMMON_SYSTEMS,
|
||||||
ExpectSuccess().
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
Run(t)
|
runTestCommands(t, r, time.Second*10, []expect.Batcher{
|
||||||
})
|
&expect.BSnd{S: "alr version\n"},
|
||||||
|
&expect.BExp{R: `^v\d+\.\d+\.\d+(?:-\d+-g[a-f0-9]+)?\n$`},
|
||||||
|
&expect.BSnd{S: "echo $?\n"},
|
||||||
|
&expect.BExp{R: `^0\n$`},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
150
fix.go
150
fix.go
@@ -23,7 +23,6 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
@@ -34,28 +33,14 @@ import (
|
|||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// execWithPrivileges выполняет команду напрямую если root или CI, иначе через sudo
|
|
||||||
func execWithPrivileges(name string, args ...string) *exec.Cmd {
|
|
||||||
isRoot := os.Geteuid() == 0
|
|
||||||
isCI := os.Getenv("CI") == "true"
|
|
||||||
|
|
||||||
if !isRoot && !isCI {
|
|
||||||
// Если не root и не в CI, используем sudo
|
|
||||||
allArgs := append([]string{name}, args...)
|
|
||||||
return exec.Command("sudo", allArgs...)
|
|
||||||
} else {
|
|
||||||
// Если root или в CI, запускаем напрямую
|
|
||||||
return exec.Command(name, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FixCmd() *cli.Command {
|
func FixCmd() *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "fix",
|
Name: "fix",
|
||||||
Usage: gotext.Get("Attempt to fix problems with ALR"),
|
Usage: gotext.Get("Attempt to fix problems with ALR"),
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
// Команда выполняется от текущего пользователя
|
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
|
||||||
// При необходимости будет запрошен sudo для удаления файлов root
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := c.Context
|
ctx := c.Context
|
||||||
|
|
||||||
@@ -72,126 +57,37 @@ func FixCmd() *cli.Command {
|
|||||||
|
|
||||||
paths := cfg.GetPaths()
|
paths := cfg.GetPaths()
|
||||||
|
|
||||||
slog.Info(gotext.Get("Clearing cache and temporary directories"))
|
slog.Info(gotext.Get("Clearing cache directory"))
|
||||||
|
|
||||||
// Проверяем, существует ли директория кэша
|
|
||||||
dir, err := os.Open(paths.CacheDir)
|
dir, err := os.Open(paths.CacheDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err)
|
||||||
// Директория не существует, просто создадим её позже
|
}
|
||||||
slog.Info(gotext.Get("Cache directory does not exist, will create it"))
|
defer dir.Close()
|
||||||
} else {
|
|
||||||
return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defer dir.Close()
|
|
||||||
|
|
||||||
entries, err := dir.Readdirnames(-1)
|
entries, err := dir.Readdirnames(-1)
|
||||||
|
if err != nil {
|
||||||
|
return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
fullPath := filepath.Join(paths.CacheDir, entry)
|
||||||
|
|
||||||
|
if err := makeWritableRecursive(fullPath); err != nil {
|
||||||
|
slog.Debug("Failed to make path writable", "path", fullPath, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err)
|
return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err)
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range entries {
|
|
||||||
fullPath := filepath.Join(paths.CacheDir, entry)
|
|
||||||
|
|
||||||
// Пробуем сделать файлы доступными для записи
|
|
||||||
if err := makeWritableRecursive(fullPath); err != nil {
|
|
||||||
slog.Debug("Failed to make path writable", "path", fullPath, "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Пробуем удалить
|
|
||||||
err = os.RemoveAll(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
// Если не получилось удалить, пробуем через sudo
|
|
||||||
slog.Warn(gotext.Get("Unable to remove cache item (%s) as current user, trying with sudo", entry))
|
|
||||||
|
|
||||||
sudoCmd := execWithPrivileges("rm", "-rf", fullPath)
|
|
||||||
if sudoErr := sudoCmd.Run(); sudoErr != nil {
|
|
||||||
// Если и через sudo не получилось, пропускаем с предупреждением
|
|
||||||
slog.Error(gotext.Get("Unable to remove cache item (%s)", entry), "error", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Очищаем временные директории
|
|
||||||
slog.Info(gotext.Get("Clearing temporary directory"))
|
|
||||||
tmpDir := "/tmp/alr"
|
|
||||||
if _, err := os.Stat(tmpDir); err == nil {
|
|
||||||
// Директория существует, пробуем очистить
|
|
||||||
err = os.RemoveAll(tmpDir)
|
|
||||||
if err != nil {
|
|
||||||
// Если не получилось удалить, пробуем через sudo
|
|
||||||
slog.Warn(gotext.Get("Unable to remove temporary directory as current user, trying with sudo"))
|
|
||||||
sudoCmd := execWithPrivileges("rm", "-rf", tmpDir)
|
|
||||||
if sudoErr := sudoCmd.Run(); sudoErr != nil {
|
|
||||||
slog.Error(gotext.Get("Unable to remove temporary directory"), "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем базовый каталог /tmp/alr с владельцем root:wheel и правами 775
|
|
||||||
err = utils.EnsureTempDirWithRootOwner(tmpDir, 0o775)
|
|
||||||
if err != nil {
|
|
||||||
slog.Warn(gotext.Get("Unable to create temporary directory"), "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем каталог dl с правами для группы wheel
|
|
||||||
dlDir := filepath.Join(tmpDir, "dl")
|
|
||||||
err = utils.EnsureTempDirWithRootOwner(dlDir, 0o775)
|
|
||||||
if err != nil {
|
|
||||||
slog.Warn(gotext.Get("Unable to create download directory"), "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем каталог pkgs с правами для группы wheel
|
|
||||||
pkgsDir := filepath.Join(tmpDir, "pkgs")
|
|
||||||
err = utils.EnsureTempDirWithRootOwner(pkgsDir, 0o775)
|
|
||||||
if err != nil {
|
|
||||||
slog.Warn(gotext.Get("Unable to create packages directory"), "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Исправляем права на все существующие файлы в /tmp/alr, если там что-то есть
|
|
||||||
if _, err := os.Stat(tmpDir); err == nil {
|
|
||||||
slog.Info(gotext.Get("Fixing permissions on temporary files"))
|
|
||||||
|
|
||||||
// Проверяем, есть ли файлы в директории
|
|
||||||
entries, err := os.ReadDir(tmpDir)
|
|
||||||
if err == nil && len(entries) > 0 {
|
|
||||||
fixCmd := execWithPrivileges("chown", "-R", "root:wheel", tmpDir)
|
|
||||||
if fixErr := fixCmd.Run(); fixErr != nil {
|
|
||||||
slog.Warn(gotext.Get("Unable to fix file ownership"), "error", fixErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
fixCmd = execWithPrivileges("chmod", "-R", "2775", tmpDir)
|
|
||||||
if fixErr := fixCmd.Run(); fixErr != nil {
|
|
||||||
slog.Warn(gotext.Get("Unable to fix file permissions"), "error", fixErr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info(gotext.Get("Rebuilding cache"))
|
slog.Info(gotext.Get("Rebuilding cache"))
|
||||||
|
|
||||||
// Пробуем создать директорию кэша
|
err = os.MkdirAll(paths.CacheDir, 0o755)
|
||||||
err = os.MkdirAll(paths.CacheDir, 0o775)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Если не получилось, пробуем через sudo с правильными правами для группы wheel
|
return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err)
|
||||||
slog.Info(gotext.Get("Creating cache directory with sudo"))
|
|
||||||
sudoCmd := execWithPrivileges("mkdir", "-p", paths.CacheDir)
|
|
||||||
if sudoErr := sudoCmd.Run(); sudoErr != nil {
|
|
||||||
return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Устанавливаем права 775 и группу wheel
|
|
||||||
chmodCmd := execWithPrivileges("chmod", "775", paths.CacheDir)
|
|
||||||
if chmodErr := chmodCmd.Run(); chmodErr != nil {
|
|
||||||
return cliutils.FormatCliExit(gotext.Get("Unable to set cache directory permissions"), chmodErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
chgrpCmd := execWithPrivileges("chgrp", "wheel", paths.CacheDir)
|
|
||||||
if chgrpErr := chgrpCmd.Run(); chgrpErr != nil {
|
|
||||||
return cliutils.FormatCliExit(gotext.Get("Unable to set cache directory group"), chgrpErr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deps, err = appbuilder.
|
deps, err = appbuilder.
|
||||||
|
23
gen.go
23
gen.go
@@ -61,29 +61,6 @@ func GenCmd() *cli.Command {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "aur",
|
|
||||||
Usage: gotext.Get("Generate a ALR script for an AUR package"),
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Aliases: []string{"n"},
|
|
||||||
Required: true,
|
|
||||||
Usage: gotext.Get("Name of the AUR package"),
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "version",
|
|
||||||
Aliases: []string{"v"},
|
|
||||||
Usage: gotext.Get("Version of the package (optional, uses latest if not specified)"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
return gen.AUR(os.Stdout, gen.AUROptions{
|
|
||||||
Name: c.String("name"),
|
|
||||||
Version: c.String("version"),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,416 +0,0 @@
|
|||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/format"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MethodInfo struct {
|
|
||||||
Name string
|
|
||||||
Params []ParamInfo
|
|
||||||
Results []ResultInfo
|
|
||||||
EntityName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParamInfo struct {
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResultInfo struct {
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
Index int
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractImports(node *ast.File) []string {
|
|
||||||
var imports []string
|
|
||||||
for _, imp := range node.Imports {
|
|
||||||
if imp.Path.Value != "" {
|
|
||||||
imports = append(imports, imp.Path.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return imports
|
|
||||||
}
|
|
||||||
|
|
||||||
func output(path string, buf bytes.Buffer) {
|
|
||||||
formatted, err := format.Source(buf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("formatting: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
outPath := strings.TrimSuffix(path, ".go") + "_gen.go"
|
|
||||||
outFile, err := os.Create(outPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("create file: %v", err)
|
|
||||||
}
|
|
||||||
_, err = outFile.Write(formatted)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("writing output: %v", err)
|
|
||||||
}
|
|
||||||
outFile.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
path := os.Getenv("GOFILE")
|
|
||||||
if path == "" {
|
|
||||||
log.Fatal("GOFILE must be set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
|
||||||
log.Fatal("At least one entity name must be provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
entityNames := os.Args[1:]
|
|
||||||
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
node, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("parsing file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
packageName := node.Name.Name
|
|
||||||
|
|
||||||
// Find all specified entities
|
|
||||||
entityData := make(map[string][]*ast.Field)
|
|
||||||
|
|
||||||
for _, decl := range node.Decls {
|
|
||||||
genDecl, ok := decl.(*ast.GenDecl)
|
|
||||||
if !ok || genDecl.Tok != token.TYPE {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, spec := range genDecl.Specs {
|
|
||||||
typeSpec := spec.(*ast.TypeSpec)
|
|
||||||
for _, entityName := range entityNames {
|
|
||||||
if typeSpec.Name.Name == entityName {
|
|
||||||
interfaceType, ok := typeSpec.Type.(*ast.InterfaceType)
|
|
||||||
if !ok {
|
|
||||||
log.Fatalf("entity %s is not an interface", entityName)
|
|
||||||
}
|
|
||||||
entityData[entityName] = interfaceType.Methods.List
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify all entities were found
|
|
||||||
for _, entityName := range entityNames {
|
|
||||||
if _, found := entityData[entityName]; !found {
|
|
||||||
log.Fatalf("interface %s not found", entityName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
buf.WriteString(`
|
|
||||||
// DO NOT EDIT MANUALLY. This file is generated.
|
|
||||||
|
|
||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
`)
|
|
||||||
|
|
||||||
buf.WriteString(fmt.Sprintf("package %s\n", packageName))
|
|
||||||
|
|
||||||
// Generate base structures for all entities
|
|
||||||
baseStructs(&buf, entityNames, extractImports(node))
|
|
||||||
|
|
||||||
// Generate method-specific code for each entity
|
|
||||||
for _, entityName := range entityNames {
|
|
||||||
methods := parseMethodsFromFields(entityName, entityData[entityName])
|
|
||||||
argsGen(&buf, methods)
|
|
||||||
}
|
|
||||||
|
|
||||||
output(path, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseMethodsFromFields(entityName string, fields []*ast.Field) []MethodInfo {
|
|
||||||
var methods []MethodInfo
|
|
||||||
|
|
||||||
for _, field := range fields {
|
|
||||||
if len(field.Names) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
methodName := field.Names[0].Name
|
|
||||||
funcType, ok := field.Type.(*ast.FuncType)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
method := MethodInfo{
|
|
||||||
Name: methodName,
|
|
||||||
EntityName: entityName,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parameters, excluding context.Context
|
|
||||||
if funcType.Params != nil {
|
|
||||||
for i, param := range funcType.Params.List {
|
|
||||||
paramType := typeToString(param.Type)
|
|
||||||
// Skip context.Context parameters
|
|
||||||
if paramType == "context.Context" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(param.Names) == 0 {
|
|
||||||
method.Params = append(method.Params, ParamInfo{
|
|
||||||
Name: fmt.Sprintf("Arg%d", i),
|
|
||||||
Type: paramType,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
for _, name := range param.Names {
|
|
||||||
method.Params = append(method.Params, ParamInfo{
|
|
||||||
Name: cases.Title(language.Und, cases.NoLower).String(name.Name),
|
|
||||||
Type: paramType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse results
|
|
||||||
if funcType.Results != nil {
|
|
||||||
resultIndex := 0
|
|
||||||
for _, result := range funcType.Results.List {
|
|
||||||
resultType := typeToString(result.Type)
|
|
||||||
if resultType == "error" {
|
|
||||||
continue // Skip error in response struct
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(result.Names) == 0 {
|
|
||||||
method.Results = append(method.Results, ResultInfo{
|
|
||||||
Name: fmt.Sprintf("Result%d", resultIndex),
|
|
||||||
Type: resultType,
|
|
||||||
Index: resultIndex,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
for _, name := range result.Names {
|
|
||||||
method.Results = append(method.Results, ResultInfo{
|
|
||||||
Name: cases.Title(language.Und, cases.NoLower).String(name.Name),
|
|
||||||
Type: resultType,
|
|
||||||
Index: resultIndex,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resultIndex++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
methods = append(methods, method)
|
|
||||||
}
|
|
||||||
|
|
||||||
return methods
|
|
||||||
}
|
|
||||||
|
|
||||||
func argsGen(buf *bytes.Buffer, methods []MethodInfo) {
|
|
||||||
// Add template functions first
|
|
||||||
funcMap := template.FuncMap{
|
|
||||||
"lowerFirst": func(s string) string {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return strings.ToLower(s[:1]) + s[1:]
|
|
||||||
},
|
|
||||||
"zeroValue": func(typeName string) string {
|
|
||||||
typeName = strings.TrimSpace(typeName)
|
|
||||||
|
|
||||||
switch typeName {
|
|
||||||
case "string":
|
|
||||||
return "\"\""
|
|
||||||
case "int", "int8", "int16", "int32", "int64":
|
|
||||||
return "0"
|
|
||||||
case "uint", "uint8", "uint16", "uint32", "uint64":
|
|
||||||
return "0"
|
|
||||||
case "float32", "float64":
|
|
||||||
return "0.0"
|
|
||||||
case "bool":
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(typeName, "*") {
|
|
||||||
return "nil"
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(typeName, "[]") ||
|
|
||||||
strings.HasPrefix(typeName, "map[") ||
|
|
||||||
strings.HasPrefix(typeName, "chan ") {
|
|
||||||
return "nil"
|
|
||||||
}
|
|
||||||
|
|
||||||
if typeName == "interface{}" {
|
|
||||||
return "nil"
|
|
||||||
}
|
|
||||||
|
|
||||||
// If external type: pkg.Type
|
|
||||||
if strings.Contains(typeName, ".") {
|
|
||||||
return typeName + "{}"
|
|
||||||
}
|
|
||||||
|
|
||||||
// If starts with uppercase — likely struct
|
|
||||||
if len(typeName) > 0 && unicode.IsUpper(rune(typeName[0])) {
|
|
||||||
return typeName + "{}"
|
|
||||||
}
|
|
||||||
|
|
||||||
return "nil"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
argsTemplate := template.Must(template.New("args").Funcs(funcMap).Parse(`
|
|
||||||
{{range .}}
|
|
||||||
type {{.EntityName}}{{.Name}}Args struct {
|
|
||||||
{{range .Params}} {{.Name}} {{.Type}}
|
|
||||||
{{end}}}
|
|
||||||
|
|
||||||
type {{.EntityName}}{{.Name}}Resp struct {
|
|
||||||
{{range .Results}} {{.Name}} {{.Type}}
|
|
||||||
{{end}}}
|
|
||||||
|
|
||||||
func (s *{{.EntityName}}RPC) {{.Name}}(ctx context.Context, {{range $i, $p := .Params}}{{if $i}}, {{end}}{{lowerFirst $p.Name}} {{$p.Type}}{{end}}) ({{range $i, $r := .Results}}{{if $i}}, {{end}}{{$r.Type}}{{end}}{{if .Results}}, {{end}}error) {
|
|
||||||
var resp *{{.EntityName}}{{.Name}}Resp
|
|
||||||
err := s.client.Call("Plugin.{{.Name}}", &{{.EntityName}}{{.Name}}Args{
|
|
||||||
{{range .Params}} {{.Name}}: {{lowerFirst .Name}},
|
|
||||||
{{end}} }, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return {{range $i, $r := .Results}}{{if $i}}, {{end}}{{zeroValue $r.Type}}{{end}}{{if .Results}}, {{end}}err
|
|
||||||
}
|
|
||||||
return {{range $i, $r := .Results}}{{if $i}}, {{end}}resp.{{$r.Name}}{{end}}{{if .Results}}, {{end}}nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *{{.EntityName}}RPCServer) {{.Name}}(args *{{.EntityName}}{{.Name}}Args, resp *{{.EntityName}}{{.Name}}Resp) error {
|
|
||||||
{{if .Results}}{{range $i, $r := .Results}}{{if $i}}, {{end}}{{lowerFirst $r.Name}}{{end}}, err := {{else}}err := {{end}}s.Impl.{{.Name}}(context.Background(),{{range $i, $p := .Params}}{{if $i}}, {{end}}args.{{$p.Name}}{{end}})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
{{if .Results}}*resp = {{.EntityName}}{{.Name}}Resp{
|
|
||||||
{{range .Results}} {{.Name}}: {{lowerFirst .Name}},
|
|
||||||
{{end}} }
|
|
||||||
{{else}}*resp = {{.EntityName}}{{.Name}}Resp{}
|
|
||||||
{{end}}return nil
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
`))
|
|
||||||
|
|
||||||
err := argsTemplate.Execute(buf, methods)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("execute args template: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func typeToString(expr ast.Expr) string {
|
|
||||||
switch t := expr.(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
return t.Name
|
|
||||||
case *ast.StarExpr:
|
|
||||||
return "*" + typeToString(t.X)
|
|
||||||
case *ast.ArrayType:
|
|
||||||
return "[]" + typeToString(t.Elt)
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
xStr := typeToString(t.X)
|
|
||||||
if xStr == "context" && t.Sel.Name == "Context" {
|
|
||||||
return "context.Context"
|
|
||||||
}
|
|
||||||
return xStr + "." + t.Sel.Name
|
|
||||||
case *ast.InterfaceType:
|
|
||||||
return "interface{}"
|
|
||||||
default:
|
|
||||||
return "interface{}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func baseStructs(buf *bytes.Buffer, entityNames, imports []string) {
|
|
||||||
// Ensure "context" is included in imports
|
|
||||||
updatedImports := imports
|
|
||||||
hasContext := false
|
|
||||||
for _, imp := range imports {
|
|
||||||
if strings.Contains(imp, `"context"`) {
|
|
||||||
hasContext = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasContext {
|
|
||||||
updatedImports = append(updatedImports, `"context"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
contentTemplate := template.Must(template.New("").Parse(`
|
|
||||||
import (
|
|
||||||
"net/rpc"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-plugin"
|
|
||||||
{{range .Imports}} {{.}}
|
|
||||||
{{end}}
|
|
||||||
)
|
|
||||||
|
|
||||||
{{range .EntityNames}}
|
|
||||||
type {{ . }}Plugin struct {
|
|
||||||
Impl {{ . }}
|
|
||||||
}
|
|
||||||
|
|
||||||
type {{ . }}RPCServer struct {
|
|
||||||
Impl {{ . }}
|
|
||||||
}
|
|
||||||
|
|
||||||
type {{ . }}RPC struct {
|
|
||||||
client *rpc.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *{{ . }}Plugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
|
||||||
return &{{ . }}RPC{client: c}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *{{ . }}Plugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
|
||||||
return &{{ . }}RPCServer{Impl: p.Impl}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
{{end}}
|
|
||||||
`))
|
|
||||||
err := contentTemplate.Execute(buf, struct {
|
|
||||||
EntityNames []string
|
|
||||||
Imports []string
|
|
||||||
}{
|
|
||||||
EntityNames: entityNames,
|
|
||||||
Imports: updatedImports,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("execute template: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
39
go.mod
39
go.mod
@@ -1,6 +1,8 @@
|
|||||||
module gitea.plemya-x.ru/Plemya-x/ALR
|
module gitea.plemya-x.ru/Plemya-x/ALR
|
||||||
|
|
||||||
go 1.24.4
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3
|
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3
|
||||||
@@ -8,10 +10,12 @@ require (
|
|||||||
github.com/PuerkitoBio/purell v1.2.0
|
github.com/PuerkitoBio/purell v1.2.0
|
||||||
github.com/alecthomas/chroma/v2 v2.9.1
|
github.com/alecthomas/chroma/v2 v2.9.1
|
||||||
github.com/bmatcuk/doublestar/v4 v4.8.1
|
github.com/bmatcuk/doublestar/v4 v4.8.1
|
||||||
|
github.com/caarlos0/env v3.5.0+incompatible
|
||||||
github.com/charmbracelet/bubbles v0.20.0
|
github.com/charmbracelet/bubbles v0.20.0
|
||||||
github.com/charmbracelet/bubbletea v1.2.4
|
github.com/charmbracelet/bubbletea v1.2.4
|
||||||
github.com/charmbracelet/lipgloss v1.0.0
|
github.com/charmbracelet/lipgloss v1.0.0
|
||||||
github.com/charmbracelet/log v0.4.0
|
github.com/charmbracelet/log v0.4.0
|
||||||
|
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0
|
||||||
github.com/go-git/go-billy/v5 v5.6.0
|
github.com/go-git/go-billy/v5 v5.6.0
|
||||||
github.com/go-git/go-git/v5 v5.13.0
|
github.com/go-git/go-git/v5 v5.13.0
|
||||||
github.com/goccy/go-yaml v1.18.0
|
github.com/goccy/go-yaml v1.18.0
|
||||||
@@ -20,11 +24,6 @@ require (
|
|||||||
github.com/hashicorp/go-hclog v0.14.1
|
github.com/hashicorp/go-hclog v0.14.1
|
||||||
github.com/hashicorp/go-plugin v1.6.3
|
github.com/hashicorp/go-plugin v1.6.3
|
||||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
|
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
|
||||||
github.com/knadh/koanf/parsers/toml/v2 v2.2.0
|
|
||||||
github.com/knadh/koanf/providers/confmap v1.0.0
|
|
||||||
github.com/knadh/koanf/providers/env v1.1.0
|
|
||||||
github.com/knadh/koanf/providers/file v1.2.0
|
|
||||||
github.com/knadh/koanf/v2 v2.2.1
|
|
||||||
github.com/leonelquinteros/gotext v1.7.0
|
github.com/leonelquinteros/gotext v1.7.0
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||||
@@ -32,10 +31,9 @@ require (
|
|||||||
github.com/muesli/reflow v0.3.0
|
github.com/muesli/reflow v0.3.0
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
|
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
github.com/urfave/cli/v2 v2.25.7
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5
|
github.com/vmihailenco/msgpack/v5 v5.3.5
|
||||||
go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9
|
|
||||||
go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9
|
|
||||||
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
|
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
|
||||||
golang.org/x/crypto v0.36.0
|
golang.org/x/crypto v0.36.0
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||||
@@ -73,21 +71,21 @@ require (
|
|||||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||||
github.com/dsnet/compress v0.0.1 // indirect
|
github.com/dsnet/compress v0.0.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/efficientgo/core v1.0.0-rc.0 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
github.com/fatih/color v1.7.0 // indirect
|
github.com/fatih/color v1.7.0 // indirect
|
||||||
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/gkampitakis/ciinfo v0.3.2 // indirect
|
|
||||||
github.com/gkampitakis/go-diff v1.3.2 // indirect
|
|
||||||
github.com/gkampitakis/go-snaps v0.5.13 // indirect
|
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||||
github.com/gobwas/glob v0.2.3 // indirect
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
github.com/goccy/go-json v0.8.1 // indirect
|
github.com/goccy/go-json v0.8.1 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect
|
||||||
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect
|
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/goreleaser/chglog v0.6.1 // indirect
|
github.com/goreleaser/chglog v0.6.1 // indirect
|
||||||
@@ -104,10 +102,15 @@ require (
|
|||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
github.com/knadh/koanf/maps v0.1.2 // indirect
|
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/knadh/koanf/parsers/json v1.0.0 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/knadh/koanf/parsers/toml/v2 v2.2.0 // indirect
|
||||||
|
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
|
||||||
|
github.com/knadh/koanf/providers/env v1.1.0 // indirect
|
||||||
|
github.com/knadh/koanf/providers/file v1.2.0 // indirect
|
||||||
|
github.com/knadh/koanf/providers/rawbytes v1.0.0 // indirect
|
||||||
|
github.com/knadh/koanf/providers/structs v1.0.0 // indirect
|
||||||
|
github.com/knadh/koanf/v2 v2.2.1 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/maruel/natural v1.1.1 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
@@ -126,7 +129,6 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
github.com/shopspring/decimal v1.3.1 // indirect
|
github.com/shopspring/decimal v1.3.1 // indirect
|
||||||
@@ -134,10 +136,6 @@ require (
|
|||||||
github.com/spf13/cast v1.7.1 // indirect
|
github.com/spf13/cast v1.7.1 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
github.com/therootcompany/xz v1.0.1 // indirect
|
github.com/therootcompany/xz v1.0.1 // indirect
|
||||||
github.com/tidwall/gjson v1.18.0 // indirect
|
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
|
||||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
@@ -146,6 +144,7 @@ require (
|
|||||||
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
|
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
|
||||||
golang.org/x/mod v0.19.0 // indirect
|
golang.org/x/mod v0.19.0 // indirect
|
||||||
golang.org/x/net v0.38.0 // indirect
|
golang.org/x/net v0.38.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.25.0 // indirect
|
||||||
golang.org/x/sync v0.12.0 // indirect
|
golang.org/x/sync v0.12.0 // indirect
|
||||||
golang.org/x/term v0.30.0 // indirect
|
golang.org/x/term v0.30.0 // indirect
|
||||||
golang.org/x/tools v0.23.0 // indirect
|
golang.org/x/tools v0.23.0 // indirect
|
||||||
|
74
go.sum
74
go.sum
@@ -63,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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||||
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
|
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
|
||||||
@@ -75,11 +77,15 @@ github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
|
|||||||
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||||
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
||||||
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
||||||
|
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
|
||||||
|
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
|
||||||
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
|
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
|
||||||
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
|
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
|
||||||
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
|
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
|
||||||
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
|
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
|
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
|
||||||
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
|
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
|
||||||
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
|
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
|
||||||
@@ -104,7 +110,6 @@ github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEyc
|
|||||||
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||||
@@ -120,6 +125,10 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh
|
|||||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/efficientgo/core v1.0.0-rc.0 h1:jJoA0N+C4/knWYVZ6GrdHOtDyrg8Y/TR4vFpTaqTsqs=
|
||||||
|
github.com/efficientgo/core v1.0.0-rc.0/go.mod h1:kQa0V74HNYMfuJH6jiPiwNdpWXl4xd/K4tzlrcvYDQI=
|
||||||
|
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 h1:C/FNIs+MtAJgQYLJ9FX/ACFYyDRuLYoXTmueErrOJyA=
|
||||||
|
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0/go.mod h1:plsKU0YHE9uX+7utvr7SiDtVBSHJyEfHRO4UnUgDmts=
|
||||||
github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug=
|
github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug=
|
||||||
github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
|
github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
@@ -130,17 +139,13 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
|
|||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
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 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
|
|
||||||
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
|
||||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
|
||||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
|
||||||
github.com/gkampitakis/go-snaps v0.5.13 h1:Hhjmvv1WboSCxkR9iU2mj5PQ8tsz/y8ECGrIbjjPF8Q=
|
|
||||||
github.com/gkampitakis/go-snaps v0.5.13/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
|
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
@@ -159,8 +164,8 @@ github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7
|
|||||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
|
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
|
||||||
@@ -199,6 +204,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
|
||||||
|
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
@@ -253,6 +260,8 @@ github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3m
|
|||||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||||
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||||
|
github.com/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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
@@ -272,6 +281,8 @@ github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU
|
|||||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
||||||
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||||
|
github.com/knadh/koanf/parsers/json v1.0.0 h1:1pVR1JhMwbqSg5ICzU+surJmeBbdT4bQm7jjgnA+f8o=
|
||||||
|
github.com/knadh/koanf/parsers/json v1.0.0/go.mod h1:zb5WtibRdpxSoSJfXysqGbVxvbszdlroWDHGdDkkEYU=
|
||||||
github.com/knadh/koanf/parsers/toml/v2 v2.2.0 h1:2nV7tHYJ5OZy2BynQ4mOJ6k5bDqbbCzRERLUKBytz3A=
|
github.com/knadh/koanf/parsers/toml/v2 v2.2.0 h1:2nV7tHYJ5OZy2BynQ4mOJ6k5bDqbbCzRERLUKBytz3A=
|
||||||
github.com/knadh/koanf/parsers/toml/v2 v2.2.0/go.mod h1:JpjTeK1Ge1hVX0wbof5DMCuDBriR8bWgeQP98eeOZpI=
|
github.com/knadh/koanf/parsers/toml/v2 v2.2.0/go.mod h1:JpjTeK1Ge1hVX0wbof5DMCuDBriR8bWgeQP98eeOZpI=
|
||||||
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
|
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
|
||||||
@@ -280,6 +291,10 @@ github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWy
|
|||||||
github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY=
|
github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY=
|
||||||
github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U=
|
github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U=
|
||||||
github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
||||||
|
github.com/knadh/koanf/providers/rawbytes v1.0.0 h1:MrKDh/HksJlKJmaZjgs4r8aVBb/zsJyc/8qaSnzcdNI=
|
||||||
|
github.com/knadh/koanf/providers/rawbytes v1.0.0/go.mod h1:KxwYJf1uezTKy6PBtfE+m725NGp4GPVA7XoNTJ/PtLo=
|
||||||
|
github.com/knadh/koanf/providers/structs v1.0.0 h1:DznjB7NQykhqCar2LvNug3MuxEQsZ5KvfgMbio+23u4=
|
||||||
|
github.com/knadh/koanf/providers/structs v1.0.0/go.mod h1:kjo5TFtgpaZORlpoJqcbeLowM2cINodv8kX+oFAeQ1w=
|
||||||
github.com/knadh/koanf/v2 v2.2.1 h1:jaleChtw85y3UdBnI0wCqcg1sj1gPoz6D3caGNHtrNE=
|
github.com/knadh/koanf/v2 v2.2.1 h1:jaleChtw85y3UdBnI0wCqcg1sj1gPoz6D3caGNHtrNE=
|
||||||
github.com/knadh/koanf/v2 v2.2.1/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
|
github.com/knadh/koanf/v2 v2.2.1/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
@@ -293,8 +308,6 @@ github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQ
|
|||||||
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
|
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
|
|
||||||
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
|
||||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
@@ -313,6 +326,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
|
|||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
|
||||||
@@ -338,6 +353,8 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
|||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
|
||||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
||||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||||
@@ -348,18 +365,27 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
|||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
|
||||||
|
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||||
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.36.0 h1:78hJTing+BLYLjhXE+Z2BubeEymH5Lr0/Mt8FKkxxYo=
|
||||||
|
github.com/prometheus/common v0.36.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||||
|
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||||
|
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
@@ -368,7 +394,6 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
|||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
@@ -402,18 +427,10 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
|||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
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 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
|
||||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
|
||||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
|
||||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
|
||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
|
||||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
@@ -432,12 +449,6 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr
|
|||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
|
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
|
||||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
|
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
|
||||||
go.alt-gnome.ru/capytest v0.0.2 h1:clmvIqmYS86hhA1rsvivSSPpfOFkJTpbn38EQP7I3E8=
|
|
||||||
go.alt-gnome.ru/capytest v0.0.2/go.mod h1:lvxPx3H6h+LPnStBFblgoT2wkjv0wbug3S14troykEg=
|
|
||||||
go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9 h1:NST+V5LV/eLgs0p6PsuvfHiZ4UrIWqftCdifO8zgg0g=
|
|
||||||
go.alt-gnome.ru/capytest v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:qiM8LARP+JBZr5mrDoVylOoqjrN0MAzvZ21NR9qMc0Y=
|
|
||||||
go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9 h1:VZclgdJxARvhZ6PIWWW2hQ6Ge4XeE36pzUr/U/y62bE=
|
|
||||||
go.alt-gnome.ru/capytest/providers/podman v0.0.3-0.20250706082755-f20413e052f9/go.mod h1:Wpq1Ny3eMzADJpMJArA2TZGZbsviUBmawtEPcxnoerg=
|
|
||||||
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY=
|
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY=
|
||||||
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk=
|
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
@@ -511,6 +522,8 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr
|
|||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||||
|
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -548,6 +561,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@@ -653,6 +668,7 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
|
|||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
8
info.go
8
info.go
@@ -31,6 +31,7 @@ import (
|
|||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
)
|
)
|
||||||
@@ -47,6 +48,9 @@ func InfoCmd() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
|
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
|
||||||
|
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := c.Context
|
ctx := c.Context
|
||||||
deps, err := appbuilder.
|
deps, err := appbuilder.
|
||||||
@@ -70,7 +74,9 @@ func InfoCmd() *cli.Command {
|
|||||||
return nil
|
return nil
|
||||||
}),
|
}),
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
// Запуск от текущего пользователя
|
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
args := c.Args()
|
args := c.Args()
|
||||||
if args.Len() < 1 {
|
if args.Len() < 1 {
|
||||||
|
@@ -51,6 +51,9 @@ func InstallCmd() *cli.Command {
|
|||||||
return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil)
|
return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
installer, installerClose, err := build.GetSafeInstaller()
|
installer, installerClose, err := build.GetSafeInstaller()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -58,6 +61,9 @@ func InstallCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
defer installerClose()
|
defer installerClose()
|
||||||
|
|
||||||
|
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
scripter, scripterClose, err := build.GetSafeScriptExecutor()
|
scripter, scripterClose, err := build.GetSafeScriptExecutor()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -110,6 +116,9 @@ func InstallCmd() *cli.Command {
|
|||||||
return nil
|
return nil
|
||||||
}),
|
}),
|
||||||
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
|
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
|
||||||
|
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := c.Context
|
ctx := c.Context
|
||||||
deps, err := appbuilder.
|
deps, err := appbuilder.
|
||||||
|
196
internal.go
196
internal.go
@@ -17,8 +17,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
@@ -30,6 +36,7 @@ import (
|
|||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||||
@@ -45,6 +52,9 @@ func InternalBuildCmd() *cli.Command {
|
|||||||
|
|
||||||
slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid())
|
slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid())
|
||||||
|
|
||||||
|
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
cfg := config.New()
|
cfg := config.New()
|
||||||
err := cfg.Load()
|
err := cfg.Load()
|
||||||
@@ -74,40 +84,6 @@ func InternalBuildCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func InternalReposCmd() *cli.Command {
|
|
||||||
return &cli.Command{
|
|
||||||
Name: "_internal-repos",
|
|
||||||
HideHelp: true,
|
|
||||||
Hidden: true,
|
|
||||||
Action: utils.RootNeededAction(func(ctx *cli.Context) error {
|
|
||||||
logger.SetupForGoPlugin()
|
|
||||||
|
|
||||||
|
|
||||||
deps, err := appbuilder.
|
|
||||||
New(ctx.Context).
|
|
||||||
WithConfig().
|
|
||||||
WithDB().
|
|
||||||
WithReposNoPull().
|
|
||||||
Build()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer deps.Defer()
|
|
||||||
|
|
||||||
pluginCfg := build.GetPluginServeCommonConfig()
|
|
||||||
pluginCfg.Plugins = map[string]plugin.Plugin{
|
|
||||||
"repos": &build.ReposExecutorPlugin{
|
|
||||||
Impl: build.NewRepos(
|
|
||||||
deps.Repos,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
plugin.Serve(pluginCfg)
|
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func InternalInstallCmd() *cli.Command {
|
func InternalInstallCmd() *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "_internal-installer",
|
Name: "_internal-installer",
|
||||||
@@ -116,7 +92,16 @@ func InternalInstallCmd() *cli.Command {
|
|||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
logger.SetupForGoPlugin()
|
logger.SetupForGoPlugin()
|
||||||
|
|
||||||
// Запуск от текущего пользователя, повышение прав будет через sudo при необходимости
|
if err := utils.EnsureIsAlrUser(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before escalating the rights, we made sure that
|
||||||
|
// this is an ALR user, so it looks safe.
|
||||||
|
err := utils.EscalateToRootUid()
|
||||||
|
if err != nil {
|
||||||
|
return cliutils.FormatCliExit("cannot escalate to root", err)
|
||||||
|
}
|
||||||
|
|
||||||
deps, err := appbuilder.
|
deps, err := appbuilder.
|
||||||
New(c.Context).
|
New(c.Context).
|
||||||
@@ -140,7 +125,7 @@ func InternalInstallCmd() *cli.Command {
|
|||||||
plugin.Serve(&plugin.ServeConfig{
|
plugin.Serve(&plugin.ServeConfig{
|
||||||
HandshakeConfig: build.HandshakeConfig,
|
HandshakeConfig: build.HandshakeConfig,
|
||||||
Plugins: map[string]plugin.Plugin{
|
Plugins: map[string]plugin.Plugin{
|
||||||
"installer": &build.InstallerExecutorPlugin{
|
"installer": &build.InstallerPlugin{
|
||||||
Impl: build.NewInstaller(
|
Impl: build.NewInstaller(
|
||||||
manager.Detect(),
|
manager.Detect(),
|
||||||
),
|
),
|
||||||
@@ -153,4 +138,143 @@ func InternalInstallCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Mount(target string) (string, func(), error) {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("failed to get executable path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(exe, "_internal-temporary-mount", target)
|
||||||
|
|
||||||
|
stdoutPipe, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("failed to get stdout pipe: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stdinPipe, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("failed to get stdin pipe: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return "", nil, fmt.Errorf("failed to start mount: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(stdoutPipe)
|
||||||
|
var mountPath string
|
||||||
|
if scanner.Scan() {
|
||||||
|
mountPath = scanner.Text()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
_ = cmd.Process.Kill()
|
||||||
|
return "", nil, fmt.Errorf("failed to read mount output: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mountPath == "" {
|
||||||
|
_ = cmd.Process.Kill()
|
||||||
|
return "", nil, errors.New("mount failed: no target path returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup := func() {
|
||||||
|
slog.Debug("cleanup triggered")
|
||||||
|
_, _ = fmt.Fprintln(stdinPipe, "")
|
||||||
|
_ = cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
return mountPath, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InternalMountCmd() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "_internal-temporary-mount",
|
||||||
|
HideHelp: true,
|
||||||
|
Hidden: true,
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
logger.SetupForGoPlugin()
|
||||||
|
|
||||||
|
sourceDir := c.Args().First()
|
||||||
|
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return cliutils.FormatCliExit("cannot get current user", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, alrGid, err := utils.GetUidGidAlrUser()
|
||||||
|
if err != nil {
|
||||||
|
return cliutils.FormatCliExit("cannot get alr user", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(sourceDir); err != nil {
|
||||||
|
return cliutils.FormatCliExit(fmt.Sprintf("cannot read %s", sourceDir), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before escalating the rights, we made sure that
|
||||||
|
// 1. user in wheel group
|
||||||
|
// 2. user can access sourceDir
|
||||||
|
if err := utils.EscalateToRootUid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := syscall.Setgid(alrGid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(constants.AlrRunDir, 0o770); err != nil {
|
||||||
|
return cliutils.FormatCliExit(fmt.Sprintf("failed to create %s", constants.AlrRunDir), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Chown(constants.AlrRunDir, 0, alrGid); err != nil {
|
||||||
|
return cliutils.FormatCliExit(fmt.Sprintf("failed to chown %s", constants.AlrRunDir), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetDir := filepath.Join(constants.AlrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid()))
|
||||||
|
// 0750: owner (root) and group (alr)
|
||||||
|
if err := os.MkdirAll(targetDir, 0o750); err != nil {
|
||||||
|
return cliutils.FormatCliExit("error creating bindfs target directory", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// chown AlrRunDir/mounts/bindfs-* to (root:alr),
|
||||||
|
// so alr user can access dir
|
||||||
|
if err := os.Chown(targetDir, 0, alrGid); err != nil {
|
||||||
|
return cliutils.FormatCliExit("failed to chown bindfs directory", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindfsCmd := exec.Command(
|
||||||
|
"bindfs",
|
||||||
|
fmt.Sprintf("--map=%s/alr:@%s/@alr", u.Uid, u.Gid),
|
||||||
|
sourceDir,
|
||||||
|
targetDir,
|
||||||
|
)
|
||||||
|
|
||||||
|
bindfsCmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := bindfsCmd.Run(); err != nil {
|
||||||
|
return cliutils.FormatCliExit("failed to strart bindfs", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(targetDir)
|
||||||
|
|
||||||
|
_, _ = bufio.NewReader(os.Stdin).ReadString('\n')
|
||||||
|
|
||||||
|
slog.Debug("start unmount", "dir", targetDir)
|
||||||
|
|
||||||
|
umountCmd := exec.Command("umount", targetDir)
|
||||||
|
umountCmd.Stderr = os.Stderr
|
||||||
|
if err := umountCmd.Run(); err != nil {
|
||||||
|
return cliutils.FormatCliExit(fmt.Sprintf("failed to unmount %s", targetDir), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(targetDir); err != nil {
|
||||||
|
return cliutils.FormatCliExit(fmt.Sprintf("error removing directory %s", targetDir), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -32,7 +32,6 @@ import (
|
|||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/stats"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
@@ -177,6 +176,25 @@ type ScriptResolverExecutor interface {
|
|||||||
ResolveScript(ctx context.Context, pkg *alrsh.Package) *ScriptInfo
|
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 {
|
type CacheExecutor interface {
|
||||||
CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *alrsh.Package) (string, bool, error)
|
CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *alrsh.Package) (string, bool, error)
|
||||||
}
|
}
|
||||||
@@ -193,6 +211,14 @@ type CheckerExecutor interface {
|
|||||||
) (bool, error)
|
) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InstallerExecutor interface {
|
||||||
|
InstallLocal(paths []string, opts *manager.Opts) error
|
||||||
|
Install(pkgs []string, opts *manager.Opts) error
|
||||||
|
Remove(pkgs []string, opts *manager.Opts) error
|
||||||
|
|
||||||
|
RemoveAlreadyInstalled(pkgs []string) ([]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
type SourcesInput struct {
|
type SourcesInput struct {
|
||||||
Sources []string
|
Sources []string
|
||||||
Checksums []string
|
Checksums []string
|
||||||
@@ -320,9 +346,9 @@ func (b *Builder) BuildPackage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var builtDeps []*BuiltDep
|
var builtDeps []*BuiltDep
|
||||||
var remainingVars []*alrsh.Package
|
|
||||||
|
|
||||||
if !input.opts.Clean {
|
if !input.opts.Clean {
|
||||||
|
var remainingVars []*alrsh.Package
|
||||||
for _, vars := range varsOfPackages {
|
for _, vars := range varsOfPackages {
|
||||||
builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars)
|
builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -331,7 +357,6 @@ func (b *Builder) BuildPackage(
|
|||||||
if ok {
|
if ok {
|
||||||
builtDeps = append(builtDeps, &BuiltDep{
|
builtDeps = append(builtDeps, &BuiltDep{
|
||||||
Path: builtPkgPath,
|
Path: builtPkgPath,
|
||||||
Name: vars.Name,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
remainingVars = append(remainingVars, vars)
|
remainingVars = append(remainingVars, vars)
|
||||||
@@ -339,12 +364,8 @@ func (b *Builder) BuildPackage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(remainingVars) == 0 {
|
if len(remainingVars) == 0 {
|
||||||
slog.Info(gotext.Get("Using cached package"), "name", basePkg)
|
|
||||||
return builtDeps, nil
|
return builtDeps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем varsOfPackages только теми пакетами, которые нужно собрать
|
|
||||||
varsOfPackages = remainingVars
|
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("ViewScript")
|
slog.Debug("ViewScript")
|
||||||
@@ -407,19 +428,9 @@ func (b *Builder) BuildPackage(
|
|||||||
|
|
||||||
// We filter so as not to re-build what has already been built at the `installBuildDeps` stage.
|
// We filter so as not to re-build what has already been built at the `installBuildDeps` stage.
|
||||||
var filteredDepends []string
|
var filteredDepends []string
|
||||||
|
|
||||||
// Создаем набор подпакетов текущего мультипакета для исключения циклических зависимостей
|
|
||||||
currentPackageNames := make(map[string]struct{})
|
|
||||||
for _, pkg := range input.packages {
|
|
||||||
currentPackageNames[pkg] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range depends {
|
for _, d := range depends {
|
||||||
if _, found := depNames[d]; !found {
|
if _, found := depNames[d]; !found {
|
||||||
// Исключаем зависимости, которые являются подпакетами текущего мультипакета
|
filteredDepends = append(filteredDepends, d)
|
||||||
if _, isCurrentPackage := currentPackageNames[d]; !isCurrentPackage {
|
|
||||||
filteredDepends = append(filteredDepends, d)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +499,6 @@ func (b *Builder) removeBuildDeps(ctx context.Context, input interface {
|
|||||||
|
|
||||||
if remove {
|
if remove {
|
||||||
err = b.installerExecutor.Remove(
|
err = b.installerExecutor.Remove(
|
||||||
ctx,
|
|
||||||
deps,
|
deps,
|
||||||
&manager.Opts{
|
&manager.Opts{
|
||||||
NoConfirm: !input.BuildOpts().Interactive,
|
NoConfirm: !input.BuildOpts().Interactive,
|
||||||
@@ -535,7 +545,6 @@ func (b *Builder) InstallALRPackages(
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = b.installerExecutor.InstallLocal(
|
err = b.installerExecutor.InstallLocal(
|
||||||
ctx,
|
|
||||||
GetBuiltPaths(res),
|
GetBuiltPaths(res),
|
||||||
&manager.Opts{
|
&manager.Opts{
|
||||||
NoConfirm: !input.BuildOpts().Interactive,
|
NoConfirm: !input.BuildOpts().Interactive,
|
||||||
@@ -544,13 +553,6 @@ func (b *Builder) InstallALRPackages(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отслеживание установки ALR пакетов
|
|
||||||
for _, dep := range res {
|
|
||||||
if stats.ShouldTrackPackage(dep.Name) {
|
|
||||||
stats.TrackInstallation(ctx, dep.Name, "upgrade")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -575,13 +577,11 @@ func (b *Builder) BuildALRDeps(
|
|||||||
repoDeps = notFound
|
repoDeps = notFound
|
||||||
|
|
||||||
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
|
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
|
||||||
// Для зависимостей указываем isDependency = true
|
pkgs := cliutils.FlattenPkgs(
|
||||||
pkgs := cliutils.FlattenPkgsWithContext(
|
|
||||||
ctx,
|
ctx,
|
||||||
found,
|
found,
|
||||||
"install",
|
"install",
|
||||||
input.BuildOpts().Interactive,
|
input.BuildOpts().Interactive,
|
||||||
true,
|
|
||||||
)
|
)
|
||||||
type item struct {
|
type item struct {
|
||||||
pkg *alrsh.Package
|
pkg *alrsh.Package
|
||||||
@@ -645,7 +645,7 @@ func (i *Builder) installBuildDeps(
|
|||||||
var deps []string
|
var deps []string
|
||||||
var err error
|
var err error
|
||||||
if len(pkgs) > 0 {
|
if len(pkgs) > 0 {
|
||||||
deps, err = i.installerExecutor.RemoveAlreadyInstalled(ctx, pkgs)
|
deps, err = i.installerExecutor.RemoveAlreadyInstalled(pkgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -668,7 +668,7 @@ func (i *Builder) installOptDeps(
|
|||||||
pkgs []string,
|
pkgs []string,
|
||||||
) ([]*BuiltDep, error) {
|
) ([]*BuiltDep, error) {
|
||||||
var builtDeps []*BuiltDep
|
var builtDeps []*BuiltDep
|
||||||
optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(ctx, pkgs)
|
optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -710,35 +710,21 @@ func (i *Builder) InstallPkgs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(builtDeps) > 0 {
|
if len(builtDeps) > 0 {
|
||||||
err = i.installerExecutor.InstallLocal(ctx, GetBuiltPaths(builtDeps), &manager.Opts{
|
err = i.installerExecutor.InstallLocal(GetBuiltPaths(builtDeps), &manager.Opts{
|
||||||
NoConfirm: !input.BuildOpts().Interactive,
|
NoConfirm: !input.BuildOpts().Interactive,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отслеживание установки локальных пакетов
|
|
||||||
for _, dep := range builtDeps {
|
|
||||||
if stats.ShouldTrackPackage(dep.Name) {
|
|
||||||
stats.TrackInstallation(ctx, dep.Name, "install")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(repoDeps) > 0 {
|
if len(repoDeps) > 0 {
|
||||||
err = i.installerExecutor.Install(ctx, repoDeps, &manager.Opts{
|
err = i.installerExecutor.Install(repoDeps, &manager.Opts{
|
||||||
NoConfirm: !input.BuildOpts().Interactive,
|
NoConfirm: !input.BuildOpts().Interactive,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отслеживание установки пакетов из репозитория
|
|
||||||
for _, pkg := range repoDeps {
|
|
||||||
if stats.ShouldTrackPackage(pkg) {
|
|
||||||
stats.TrackInstallation(ctx, pkg, "install")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return builtDeps, nil
|
return builtDeps, nil
|
||||||
|
@@ -240,10 +240,39 @@ func createFirejailedBinary(
|
|||||||
return nil, fmt.Errorf("failed to create wrapper script: %w", err)
|
return nil, fmt.Errorf("failed to create wrapper script: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildContents(pkg, dirs, &[]string{
|
profile, err := getContentFromPath(dest, dirs.PkgDir)
|
||||||
origFilePath,
|
if err != nil {
|
||||||
dest,
|
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) {
|
func generateSafeName(destination string) (string, error) {
|
||||||
|
@@ -17,8 +17,6 @@
|
|||||||
package build
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,19 +28,19 @@ func NewInstaller(mgr manager.Manager) *Installer {
|
|||||||
|
|
||||||
type Installer struct{ mgr manager.Manager }
|
type Installer struct{ mgr manager.Manager }
|
||||||
|
|
||||||
func (i *Installer) InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error {
|
func (i *Installer) InstallLocal(paths []string, opts *manager.Opts) error {
|
||||||
return i.mgr.InstallLocal(opts, paths...)
|
return i.mgr.InstallLocal(opts, paths...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Installer) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error {
|
func (i *Installer) Install(pkgs []string, opts *manager.Opts) error {
|
||||||
return i.mgr.Install(opts, pkgs...)
|
return i.mgr.Install(opts, pkgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Installer) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error {
|
func (i *Installer) Remove(pkgs []string, opts *manager.Opts) error {
|
||||||
return i.mgr.Remove(opts, pkgs...)
|
return i.mgr.Remove(opts, pkgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Installer) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error) {
|
func (i *Installer) RemoveAlreadyInstalled(pkgs []string) ([]string, error) {
|
||||||
filteredPackages := []string{}
|
filteredPackages := []string{}
|
||||||
|
|
||||||
for _, dep := range pkgs {
|
for _, dep := range pkgs {
|
||||||
|
@@ -1,142 +0,0 @@
|
|||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
|
||||||
"github.com/hashicorp/go-plugin"
|
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
var pluginMap = map[string]plugin.Plugin{
|
|
||||||
"script-executor": &ScriptExecutorPlugin{},
|
|
||||||
"installer": &InstallerExecutorPlugin{},
|
|
||||||
"repos": &ReposExecutorPlugin{},
|
|
||||||
}
|
|
||||||
|
|
||||||
var HandshakeConfig = plugin.HandshakeConfig{
|
|
||||||
ProtocolVersion: 1,
|
|
||||||
MagicCookieKey: "ALR_PLUGIN",
|
|
||||||
MagicCookieValue: "-",
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCommonCmdEnv(cmd *exec.Cmd) {
|
|
||||||
cmd.Env = []string{
|
|
||||||
"HOME=" + os.Getenv("HOME"),
|
|
||||||
"LOGNAME=" + os.Getenv("USER"),
|
|
||||||
"USER=" + os.Getenv("USER"),
|
|
||||||
"PATH=/usr/bin:/bin:/usr/local/bin",
|
|
||||||
}
|
|
||||||
for _, env := range os.Environ() {
|
|
||||||
if strings.HasPrefix(env, "LANG=") ||
|
|
||||||
strings.HasPrefix(env, "LANGUAGE=") ||
|
|
||||||
strings.HasPrefix(env, "LC_") ||
|
|
||||||
strings.HasPrefix(env, "ALR_LOG_LEVEL=") {
|
|
||||||
cmd.Env = append(cmd.Env, env)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPluginServeCommonConfig() *plugin.ServeConfig {
|
|
||||||
return &plugin.ServeConfig{
|
|
||||||
HandshakeConfig: HandshakeConfig,
|
|
||||||
Logger: hclog.New(&hclog.LoggerOptions{
|
|
||||||
Name: "plugin",
|
|
||||||
Output: os.Stderr,
|
|
||||||
Level: hclog.Trace,
|
|
||||||
JSONFormat: true,
|
|
||||||
DisableTime: true,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSafeInstaller() (InstallerExecutor, func(), error) {
|
|
||||||
return getSafeExecutor[InstallerExecutor]("_internal-installer", "installer")
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSafeScriptExecutor() (ScriptExecutor, func(), error) {
|
|
||||||
return getSafeExecutor[ScriptExecutor]("_internal-safe-script-executor", "script-executor")
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSafeReposExecutor() (ReposExecutor, func(), error) {
|
|
||||||
return getSafeExecutor[ReposExecutor]("_internal-repos", "repos")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSafeExecutor[T any](subCommand, pluginName string) (T, func(), error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
executable, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
var zero T
|
|
||||||
return zero, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(executable, subCommand)
|
|
||||||
setCommonCmdEnv(cmd)
|
|
||||||
|
|
||||||
client := plugin.NewClient(&plugin.ClientConfig{
|
|
||||||
HandshakeConfig: HandshakeConfig,
|
|
||||||
Plugins: pluginMap,
|
|
||||||
Cmd: cmd,
|
|
||||||
Logger: logger.GetHCLoggerAdapter(),
|
|
||||||
SkipHostEnv: true,
|
|
||||||
UnixSocketConfig: &plugin.UnixSocketConfig{},
|
|
||||||
SyncStderr: os.Stderr,
|
|
||||||
})
|
|
||||||
rpcClient, err := client.Client()
|
|
||||||
if err != nil {
|
|
||||||
var zero T
|
|
||||||
return zero, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var cleanupOnce sync.Once
|
|
||||||
cleanup := func() {
|
|
||||||
cleanupOnce.Do(func() {
|
|
||||||
client.Kill()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
slog.Debug("close executor")
|
|
||||||
cleanup()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
raw, err := rpcClient.Dispense(pluginName)
|
|
||||||
if err != nil {
|
|
||||||
var zero T
|
|
||||||
return zero, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
executor, ok := raw.(T)
|
|
||||||
if !ok {
|
|
||||||
var zero T
|
|
||||||
err = fmt.Errorf("dispensed object is not a %T (got %T)", zero, raw)
|
|
||||||
return zero, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return executor, cleanup, nil
|
|
||||||
}
|
|
@@ -1,60 +0,0 @@
|
|||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate go run ../../generators/plugin-generator InstallerExecutor ScriptExecutor ReposExecutor
|
|
||||||
|
|
||||||
// The Executors interfaces must use context.Context as the first parameter,
|
|
||||||
// because the plugin-generator cannot generate code without it.
|
|
||||||
|
|
||||||
type InstallerExecutor interface {
|
|
||||||
InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error
|
|
||||||
Install(ctx context.Context, pkgs []string, opts *manager.Opts) error
|
|
||||||
Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error
|
|
||||||
RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutor interface {
|
|
||||||
ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error)
|
|
||||||
ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error)
|
|
||||||
PrepareDirs(
|
|
||||||
ctx context.Context,
|
|
||||||
input *BuildInput,
|
|
||||||
basePkg string,
|
|
||||||
) error
|
|
||||||
ExecuteSecondPass(
|
|
||||||
ctx context.Context,
|
|
||||||
input *BuildInput,
|
|
||||||
sf *alrsh.ScriptFile,
|
|
||||||
varsOfPackages []*alrsh.Package,
|
|
||||||
repoDeps []string,
|
|
||||||
builtDeps []*BuiltDep,
|
|
||||||
basePkg string,
|
|
||||||
) ([]*BuiltDep, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReposExecutor interface {
|
|
||||||
PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error)
|
|
||||||
}
|
|
@@ -1,369 +0,0 @@
|
|||||||
// DO NOT EDIT MANUALLY. This file is generated.
|
|
||||||
|
|
||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/rpc"
|
|
||||||
|
|
||||||
"context"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
|
||||||
"github.com/hashicorp/go-plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InstallerExecutorPlugin struct {
|
|
||||||
Impl InstallerExecutor
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstallerExecutorRPCServer struct {
|
|
||||||
Impl InstallerExecutor
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstallerExecutorRPC struct {
|
|
||||||
client *rpc.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *InstallerExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
|
||||||
return &InstallerExecutorRPC{client: c}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *InstallerExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
|
||||||
return &InstallerExecutorRPCServer{Impl: p.Impl}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutorPlugin struct {
|
|
||||||
Impl ScriptExecutor
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutorRPCServer struct {
|
|
||||||
Impl ScriptExecutor
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutorRPC struct {
|
|
||||||
client *rpc.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ScriptExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
|
||||||
return &ScriptExecutorRPC{client: c}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ScriptExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
|
||||||
return &ScriptExecutorRPCServer{Impl: p.Impl}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReposExecutorPlugin struct {
|
|
||||||
Impl ReposExecutor
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReposExecutorRPCServer struct {
|
|
||||||
Impl ReposExecutor
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReposExecutorRPC struct {
|
|
||||||
client *rpc.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReposExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
|
||||||
return &ReposExecutorRPC{client: c}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReposExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
|
||||||
return &ReposExecutorRPCServer{Impl: p.Impl}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstallerExecutorInstallLocalArgs struct {
|
|
||||||
Paths []string
|
|
||||||
Opts *manager.Opts
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstallerExecutorInstallLocalResp struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstallerExecutorRPC) InstallLocal(ctx context.Context, paths []string, opts *manager.Opts) error {
|
|
||||||
var resp *InstallerExecutorInstallLocalResp
|
|
||||||
err := s.client.Call("Plugin.InstallLocal", &InstallerExecutorInstallLocalArgs{
|
|
||||||
Paths: paths,
|
|
||||||
Opts: opts,
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstallerExecutorRPCServer) InstallLocal(args *InstallerExecutorInstallLocalArgs, resp *InstallerExecutorInstallLocalResp) error {
|
|
||||||
err := s.Impl.InstallLocal(context.Background(), args.Paths, args.Opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*resp = InstallerExecutorInstallLocalResp{}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstallerExecutorInstallArgs struct {
|
|
||||||
Pkgs []string
|
|
||||||
Opts *manager.Opts
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstallerExecutorInstallResp struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstallerExecutorRPC) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error {
|
|
||||||
var resp *InstallerExecutorInstallResp
|
|
||||||
err := s.client.Call("Plugin.Install", &InstallerExecutorInstallArgs{
|
|
||||||
Pkgs: pkgs,
|
|
||||||
Opts: opts,
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstallerExecutorRPCServer) Install(args *InstallerExecutorInstallArgs, resp *InstallerExecutorInstallResp) error {
|
|
||||||
err := s.Impl.Install(context.Background(), args.Pkgs, args.Opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*resp = InstallerExecutorInstallResp{}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstallerExecutorRemoveArgs struct {
|
|
||||||
Pkgs []string
|
|
||||||
Opts *manager.Opts
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstallerExecutorRemoveResp struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstallerExecutorRPC) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error {
|
|
||||||
var resp *InstallerExecutorRemoveResp
|
|
||||||
err := s.client.Call("Plugin.Remove", &InstallerExecutorRemoveArgs{
|
|
||||||
Pkgs: pkgs,
|
|
||||||
Opts: opts,
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstallerExecutorRPCServer) Remove(args *InstallerExecutorRemoveArgs, resp *InstallerExecutorRemoveResp) error {
|
|
||||||
err := s.Impl.Remove(context.Background(), args.Pkgs, args.Opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*resp = InstallerExecutorRemoveResp{}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstallerExecutorRemoveAlreadyInstalledArgs struct {
|
|
||||||
Pkgs []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstallerExecutorRemoveAlreadyInstalledResp struct {
|
|
||||||
Result0 []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstallerExecutorRPC) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error) {
|
|
||||||
var resp *InstallerExecutorRemoveAlreadyInstalledResp
|
|
||||||
err := s.client.Call("Plugin.RemoveAlreadyInstalled", &InstallerExecutorRemoveAlreadyInstalledArgs{
|
|
||||||
Pkgs: pkgs,
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp.Result0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InstallerExecutorRPCServer) RemoveAlreadyInstalled(args *InstallerExecutorRemoveAlreadyInstalledArgs, resp *InstallerExecutorRemoveAlreadyInstalledResp) error {
|
|
||||||
result0, err := s.Impl.RemoveAlreadyInstalled(context.Background(), args.Pkgs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*resp = InstallerExecutorRemoveAlreadyInstalledResp{
|
|
||||||
Result0: result0,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutorReadScriptArgs struct {
|
|
||||||
ScriptPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutorReadScriptResp struct {
|
|
||||||
Result0 *alrsh.ScriptFile
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
|
|
||||||
var resp *ScriptExecutorReadScriptResp
|
|
||||||
err := s.client.Call("Plugin.ReadScript", &ScriptExecutorReadScriptArgs{
|
|
||||||
ScriptPath: scriptPath,
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp.Result0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ScriptExecutorRPCServer) ReadScript(args *ScriptExecutorReadScriptArgs, resp *ScriptExecutorReadScriptResp) error {
|
|
||||||
result0, err := s.Impl.ReadScript(context.Background(), args.ScriptPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*resp = ScriptExecutorReadScriptResp{
|
|
||||||
Result0: result0,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutorExecuteFirstPassArgs struct {
|
|
||||||
Input *BuildInput
|
|
||||||
Sf *alrsh.ScriptFile
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutorExecuteFirstPassResp struct {
|
|
||||||
Result0 string
|
|
||||||
Result1 []*alrsh.Package
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
|
|
||||||
var resp *ScriptExecutorExecuteFirstPassResp
|
|
||||||
err := s.client.Call("Plugin.ExecuteFirstPass", &ScriptExecutorExecuteFirstPassArgs{
|
|
||||||
Input: input,
|
|
||||||
Sf: sf,
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return resp.Result0, resp.Result1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ScriptExecutorRPCServer) ExecuteFirstPass(args *ScriptExecutorExecuteFirstPassArgs, resp *ScriptExecutorExecuteFirstPassResp) error {
|
|
||||||
result0, result1, err := s.Impl.ExecuteFirstPass(context.Background(), args.Input, args.Sf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*resp = ScriptExecutorExecuteFirstPassResp{
|
|
||||||
Result0: result0,
|
|
||||||
Result1: result1,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutorPrepareDirsArgs struct {
|
|
||||||
Input *BuildInput
|
|
||||||
BasePkg string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutorPrepareDirsResp struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ScriptExecutorRPC) PrepareDirs(ctx context.Context, input *BuildInput, basePkg string) error {
|
|
||||||
var resp *ScriptExecutorPrepareDirsResp
|
|
||||||
err := s.client.Call("Plugin.PrepareDirs", &ScriptExecutorPrepareDirsArgs{
|
|
||||||
Input: input,
|
|
||||||
BasePkg: basePkg,
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ScriptExecutorRPCServer) PrepareDirs(args *ScriptExecutorPrepareDirsArgs, resp *ScriptExecutorPrepareDirsResp) error {
|
|
||||||
err := s.Impl.PrepareDirs(context.Background(), args.Input, args.BasePkg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*resp = ScriptExecutorPrepareDirsResp{}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutorExecuteSecondPassArgs struct {
|
|
||||||
Input *BuildInput
|
|
||||||
Sf *alrsh.ScriptFile
|
|
||||||
VarsOfPackages []*alrsh.Package
|
|
||||||
RepoDeps []string
|
|
||||||
BuiltDeps []*BuiltDep
|
|
||||||
BasePkg string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScriptExecutorExecuteSecondPassResp struct {
|
|
||||||
Result0 []*BuiltDep
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ScriptExecutorRPC) ExecuteSecondPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile, varsOfPackages []*alrsh.Package, repoDeps []string, builtDeps []*BuiltDep, basePkg string) ([]*BuiltDep, error) {
|
|
||||||
var resp *ScriptExecutorExecuteSecondPassResp
|
|
||||||
err := s.client.Call("Plugin.ExecuteSecondPass", &ScriptExecutorExecuteSecondPassArgs{
|
|
||||||
Input: input,
|
|
||||||
Sf: sf,
|
|
||||||
VarsOfPackages: varsOfPackages,
|
|
||||||
RepoDeps: repoDeps,
|
|
||||||
BuiltDeps: builtDeps,
|
|
||||||
BasePkg: basePkg,
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp.Result0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ScriptExecutorExecuteSecondPassArgs, resp *ScriptExecutorExecuteSecondPassResp) error {
|
|
||||||
result0, err := s.Impl.ExecuteSecondPass(context.Background(), args.Input, args.Sf, args.VarsOfPackages, args.RepoDeps, args.BuiltDeps, args.BasePkg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*resp = ScriptExecutorExecuteSecondPassResp{
|
|
||||||
Result0: result0,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReposExecutorPullOneAndUpdateFromConfigArgs struct {
|
|
||||||
Repo *types.Repo
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReposExecutorPullOneAndUpdateFromConfigResp struct {
|
|
||||||
Result0 types.Repo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ReposExecutorRPC) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error) {
|
|
||||||
var resp *ReposExecutorPullOneAndUpdateFromConfigResp
|
|
||||||
err := s.client.Call("Plugin.PullOneAndUpdateFromConfig", &ReposExecutorPullOneAndUpdateFromConfigArgs{
|
|
||||||
Repo: repo,
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return types.Repo{}, err
|
|
||||||
}
|
|
||||||
return resp.Result0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ReposExecutorRPCServer) PullOneAndUpdateFromConfig(args *ReposExecutorPullOneAndUpdateFromConfigArgs, resp *ReposExecutorPullOneAndUpdateFromConfigResp) error {
|
|
||||||
result0, err := s.Impl.PullOneAndUpdateFromConfig(context.Background(), args.Repo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*resp = ReposExecutorPullOneAndUpdateFromConfigResp{
|
|
||||||
Result0: result0,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -17,21 +17,24 @@
|
|||||||
package build
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
|
"strings"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type reposExecutor struct{ r *repos.Repos }
|
func setCommonCmdEnv(cmd *exec.Cmd) {
|
||||||
|
cmd.Env = []string{
|
||||||
func NewRepos(r *repos.Repos) ReposExecutor {
|
"HOME=/var/cache/alr",
|
||||||
return &reposExecutor{r}
|
"LOGNAME=alr",
|
||||||
}
|
"USER=alr",
|
||||||
|
"PATH=/usr/bin:/bin:/usr/local/bin",
|
||||||
func (r *reposExecutor) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) (types.Repo, error) {
|
}
|
||||||
if err := r.r.PullOneAndUpdateFromConfig(ctx, repo); err != nil {
|
for _, env := range os.Environ() {
|
||||||
return *repo, err
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return *repo, nil
|
|
||||||
}
|
}
|
161
internal/build/safe_installer.go
Normal file
161
internal/build/safe_installer.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// ALR - Any Linux Repository
|
||||||
|
// Copyright (C) 2025 The ALR Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// 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) Remove(pkgs []string, opts *manager.Opts) error {
|
||||||
|
return r.client.Call("Plugin.Remove", &InstallArgs{
|
||||||
|
PackagesOrPaths: pkgs,
|
||||||
|
Opts: opts,
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InstallerRPCServer) Remove(args *InstallArgs, reply *struct{}) error {
|
||||||
|
return s.Impl.Remove(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
|
||||||
|
}
|
@@ -23,7 +23,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache"
|
||||||
)
|
)
|
||||||
@@ -75,9 +74,7 @@ func (s *SourceDownloader) DownloadSources(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Используем временную директорию для загрузок
|
opts.DlCache = dlcache.New(s.cfg.GetPaths().CacheDir)
|
||||||
// dlcache.New добавит свой подкаталог "dl" внутри
|
|
||||||
opts.DlCache = dlcache.New(constants.TempDir)
|
|
||||||
|
|
||||||
err := dl.Download(ctx, opts)
|
err := dl.Download(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -19,7 +19,6 @@ package build
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -41,7 +40,6 @@ import (
|
|||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
@@ -49,22 +47,15 @@ import (
|
|||||||
|
|
||||||
// Функция prepareDirs подготавливает директории для сборки.
|
// Функция prepareDirs подготавливает директории для сборки.
|
||||||
func prepareDirs(dirs types.Directories) error {
|
func prepareDirs(dirs types.Directories) error {
|
||||||
// Пробуем удалить базовую директорию, если она существует
|
err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует
|
||||||
err := os.RemoveAll(dirs.BaseDir)
|
|
||||||
if err != nil {
|
|
||||||
// Если не можем удалить (например, принадлежит root), логируем и продолжаем
|
|
||||||
// Новые директории будут созданы или перезаписаны
|
|
||||||
slog.Debug("Failed to remove base directory", "path", dirs.BaseDir, "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем директории с правильным владельцем для /tmp/alr с setgid битом
|
|
||||||
err = utils.EnsureTempDirWithRootOwner(dirs.SrcDir, 0o2775)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = os.MkdirAll(dirs.SrcDir, 0o755) // Создаем директорию для источников
|
||||||
// Создаем директорию для пакетов с setgid битом
|
if err != nil {
|
||||||
return utils.EnsureTempDirWithRootOwner(dirs.PkgDir, 0o2775)
|
return err
|
||||||
|
}
|
||||||
|
return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
|
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
|
||||||
|
@@ -103,62 +103,22 @@ func ShowScript(path, name, style string) error {
|
|||||||
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
|
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
|
||||||
// of packages by prompting the user if multiple packages match.
|
// of packages by prompting the user if multiple packages match.
|
||||||
func FlattenPkgs(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool) []alrsh.Package {
|
func FlattenPkgs(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool) []alrsh.Package {
|
||||||
return FlattenPkgsWithContext(ctx, found, verb, interactive, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlattenPkgsWithContext расширенная версия FlattenPkgs с контекстом обработки зависимостей
|
|
||||||
func FlattenPkgsWithContext(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool, isDependency bool) []alrsh.Package {
|
|
||||||
var outPkgs []alrsh.Package
|
var outPkgs []alrsh.Package
|
||||||
for _, pkgs := range found {
|
for _, pkgs := range found {
|
||||||
if len(pkgs) > 1 {
|
if len(pkgs) > 1 && interactive {
|
||||||
// Проверяем, являются ли пакеты подпакетами одного мультипакета
|
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
|
||||||
if isMultiPackage(pkgs) && verb == "install" {
|
if err != nil {
|
||||||
// Для мультипакетов при установке ВСЕГДА берем все подпакеты без выбора
|
slog.Error(gotext.Get("Error prompting for choice of package"))
|
||||||
// Это правильное поведение как для прямой установки, так и для зависимостей
|
os.Exit(1)
|
||||||
outPkgs = append(outPkgs, pkgs...)
|
|
||||||
} else if interactive {
|
|
||||||
// Для разных пакетов с одинаковым именем - показываем меню выбора
|
|
||||||
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error(gotext.Get("Error prompting for choice of package"))
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
outPkgs = append(outPkgs, choice)
|
|
||||||
} else {
|
|
||||||
// Если не интерактивный режим - берем первый
|
|
||||||
outPkgs = append(outPkgs, pkgs[0])
|
|
||||||
}
|
}
|
||||||
} else {
|
outPkgs = append(outPkgs, choice)
|
||||||
// Если только один пакет - берем его
|
} else if len(pkgs) == 1 || !interactive {
|
||||||
outPkgs = append(outPkgs, pkgs[0])
|
outPkgs = append(outPkgs, pkgs[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return outPkgs
|
return outPkgs
|
||||||
}
|
}
|
||||||
|
|
||||||
// isMultiPackage проверяет, являются ли пакеты подпакетами одного мультипакета
|
|
||||||
func isMultiPackage(pkgs []alrsh.Package) bool {
|
|
||||||
if len(pkgs) <= 1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем, что у всех пакетов одинаковый BasePkgName и Repository
|
|
||||||
firstBasePkg := pkgs[0].BasePkgName
|
|
||||||
firstRepo := pkgs[0].Repository
|
|
||||||
|
|
||||||
if firstBasePkg == "" {
|
|
||||||
return false // Не мультипакет
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pkg := range pkgs[1:] {
|
|
||||||
if pkg.BasePkgName != firstBasePkg || pkg.Repository != firstRepo {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// PkgPrompt asks the user to choose between multiple packages.
|
// PkgPrompt asks the user to choose between multiple packages.
|
||||||
func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, interactive bool) (alrsh.Package, error) {
|
func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, interactive bool) (alrsh.Package, error) {
|
||||||
if !interactive {
|
if !interactive {
|
||||||
|
@@ -21,14 +21,11 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/knadh/koanf/providers/confmap"
|
"github.com/knadh/koanf/providers/confmap"
|
||||||
"github.com/knadh/koanf/v2"
|
"github.com/knadh/koanf/v2"
|
||||||
ktoml "github.com/knadh/koanf/parsers/toml/v2"
|
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
@@ -58,13 +55,7 @@ func defaultConfigKoanf() *koanf.Koanf {
|
|||||||
"ignorePkgUpdates": []string{},
|
"ignorePkgUpdates": []string{},
|
||||||
"logLevel": "info",
|
"logLevel": "info",
|
||||||
"autoPull": true,
|
"autoPull": true,
|
||||||
"updateSystemOnUpgrade": false,
|
"repos": []types.Repo{},
|
||||||
"repos": []types.Repo{
|
|
||||||
{
|
|
||||||
Name: "alr-default",
|
|
||||||
URL: "https://gitea.plemya-x.ru/Plemya-x/alr-default.git",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if err := k.Load(confmap.Provider(defaults, "."), nil); err != nil {
|
if err := k.Load(confmap.Provider(defaults, "."), nil); err != nil {
|
||||||
panic(k)
|
panic(k)
|
||||||
@@ -107,20 +98,8 @@ func (c *ALRConfig) Load() error {
|
|||||||
c.paths.UserConfigPath = constants.SystemConfigPath
|
c.paths.UserConfigPath = constants.SystemConfigPath
|
||||||
c.paths.CacheDir = constants.SystemCachePath
|
c.paths.CacheDir = constants.SystemCachePath
|
||||||
c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo")
|
c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo")
|
||||||
c.paths.PkgsDir = filepath.Join(constants.TempDir, "pkgs") // Перемещаем в /tmp/alr/pkgs
|
c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs")
|
||||||
c.paths.DBPath = filepath.Join(c.paths.CacheDir, "alr.db")
|
c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db")
|
||||||
|
|
||||||
// Проверяем существование кэш-директории, но не пытаемся создать
|
|
||||||
if _, err := os.Stat(c.paths.CacheDir); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("failed to check cache directory: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Выполняем миграцию конфигурации при необходимости
|
|
||||||
if err := c.migrateConfig(); err != nil {
|
|
||||||
return fmt.Errorf("failed to migrate config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -133,126 +112,6 @@ func (c *ALRConfig) ToYAML() (string, error) {
|
|||||||
return string(data), nil
|
return string(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ALRConfig) migrateConfig() error {
|
|
||||||
// Проверяем, существует ли конфигурационный файл
|
|
||||||
if _, err := os.Stat(constants.SystemConfigPath); os.IsNotExist(err) {
|
|
||||||
// Если файла нет, создаем полный конфигурационный файл с дефолтными значениями
|
|
||||||
if err := c.createDefaultConfig(); err != nil {
|
|
||||||
// Если не удается создать конфиг, это не критично - продолжаем работу
|
|
||||||
// но выводим предупреждение
|
|
||||||
fmt.Fprintf(os.Stderr, "Предупреждение: не удалось создать конфигурационный файл %s: %v\n", constants.SystemConfigPath, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Если файл существует, проверяем, есть ли в нем новая опция
|
|
||||||
if !c.System.k.Exists("updateSystemOnUpgrade") {
|
|
||||||
// Если опции нет, добавляем ее со значением по умолчанию
|
|
||||||
c.System.SetUpdateSystemOnUpgrade(false)
|
|
||||||
// Сохраняем обновленную конфигурацию
|
|
||||||
if err := c.System.Save(); err != nil {
|
|
||||||
// Если не удается сохранить - это не критично, продолжаем работу
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ALRConfig) createDefaultConfig() error {
|
|
||||||
// Проверяем, запущен ли процесс от root
|
|
||||||
if os.Getuid() != 0 {
|
|
||||||
// Если не root, пытаемся запустить создание конфига с повышением привилегий
|
|
||||||
return c.createDefaultConfigWithPrivileges()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если уже root, создаем конфиг напрямую
|
|
||||||
return c.doCreateDefaultConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ALRConfig) createDefaultConfigWithPrivileges() error {
|
|
||||||
// Если useRootCmd отключен, просто пытаемся создать без повышения привилегий
|
|
||||||
if !c.cfg.UseRootCmd {
|
|
||||||
return c.doCreateDefaultConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Определяем команду для повышения привилегий
|
|
||||||
rootCmd := c.cfg.RootCmd
|
|
||||||
if rootCmd == "" {
|
|
||||||
rootCmd = "sudo" // fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем временный файл с дефолтной конфигурацией
|
|
||||||
tmpFile, err := os.CreateTemp("", "alr-config-*.toml")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("не удалось создать временный файл: %w", err)
|
|
||||||
}
|
|
||||||
defer os.Remove(tmpFile.Name())
|
|
||||||
defer tmpFile.Close()
|
|
||||||
|
|
||||||
// Генерируем дефолтную конфигурацию во временный файл
|
|
||||||
defaults := defaultConfigKoanf()
|
|
||||||
tempSystemConfig := &SystemConfig{k: defaults}
|
|
||||||
|
|
||||||
bytes, err := tempSystemConfig.k.Marshal(ktoml.Parser())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("не удалось сериализовать конфигурацию: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := tmpFile.Write(bytes); err != nil {
|
|
||||||
return fmt.Errorf("не удалось записать во временный файл: %w", err)
|
|
||||||
}
|
|
||||||
tmpFile.Close()
|
|
||||||
|
|
||||||
// Используем команду повышения привилегий для создания директории и копирования файла
|
|
||||||
|
|
||||||
// Создаем директорию с правами
|
|
||||||
configDir := filepath.Dir(constants.SystemConfigPath)
|
|
||||||
mkdirCmd := exec.Command(rootCmd, "mkdir", "-p", configDir)
|
|
||||||
if err := mkdirCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("не удалось создать директорию %s: %w", configDir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Копируем файл в нужное место
|
|
||||||
cpCmd := exec.Command(rootCmd, "cp", tmpFile.Name(), constants.SystemConfigPath)
|
|
||||||
if err := cpCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("не удалось скопировать конфигурацию в %s: %w", constants.SystemConfigPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Устанавливаем правильные права доступа
|
|
||||||
chmodCmd := exec.Command(rootCmd, "chmod", "644", constants.SystemConfigPath)
|
|
||||||
if err := chmodCmd.Run(); err != nil {
|
|
||||||
// Не критично, продолжаем
|
|
||||||
fmt.Fprintf(os.Stderr, "Предупреждение: не удалось установить права доступа для %s: %v\n", constants.SystemConfigPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ALRConfig) doCreateDefaultConfig() error {
|
|
||||||
// Проверяем, существует ли директория для конфига
|
|
||||||
configDir := filepath.Dir(constants.SystemConfigPath)
|
|
||||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
|
||||||
// Пытаемся создать директорию
|
|
||||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
||||||
return fmt.Errorf("не удалось создать директорию %s: %w", configDir, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загружаем дефолтную конфигурацию
|
|
||||||
defaults := defaultConfigKoanf()
|
|
||||||
|
|
||||||
// Копируем все дефолтные значения в системную конфигурацию
|
|
||||||
c.System.k = defaults
|
|
||||||
|
|
||||||
// Сохраняем конфигурацию в файл
|
|
||||||
if err := c.System.Save(); err != nil {
|
|
||||||
return fmt.Errorf("не удалось сохранить конфигурацию в %s: %w", constants.SystemConfigPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ALRConfig) RootCmd() string { return c.cfg.RootCmd }
|
func (c *ALRConfig) RootCmd() string { return c.cfg.RootCmd }
|
||||||
func (c *ALRConfig) PagerStyle() string { return c.cfg.PagerStyle }
|
func (c *ALRConfig) PagerStyle() string { return c.cfg.PagerStyle }
|
||||||
func (c *ALRConfig) AutoPull() bool { return c.cfg.AutoPull }
|
func (c *ALRConfig) AutoPull() bool { return c.cfg.AutoPull }
|
||||||
@@ -261,5 +120,4 @@ func (c *ALRConfig) SetRepos(repos []types.Repo) { c.System.SetRepos(repos) }
|
|||||||
func (c *ALRConfig) IgnorePkgUpdates() []string { return c.cfg.IgnorePkgUpdates }
|
func (c *ALRConfig) IgnorePkgUpdates() []string { return c.cfg.IgnorePkgUpdates }
|
||||||
func (c *ALRConfig) LogLevel() string { return c.cfg.LogLevel }
|
func (c *ALRConfig) LogLevel() string { return c.cfg.LogLevel }
|
||||||
func (c *ALRConfig) UseRootCmd() bool { return c.cfg.UseRootCmd }
|
func (c *ALRConfig) UseRootCmd() bool { return c.cfg.UseRootCmd }
|
||||||
func (c *ALRConfig) UpdateSystemOnUpgrade() bool { return c.cfg.UpdateSystemOnUpgrade }
|
|
||||||
func (c *ALRConfig) GetPaths() *Paths { return c.paths }
|
func (c *ALRConfig) GetPaths() *Paths { return c.paths }
|
||||||
|
@@ -142,10 +142,3 @@ func (c *SystemConfig) SetRepos(v []types.Repo) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SystemConfig) SetUpdateSystemOnUpgrade(v bool) {
|
|
||||||
err := c.k.Set("updateSystemOnUpgrade", v)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -19,6 +19,6 @@ package constants
|
|||||||
const (
|
const (
|
||||||
SystemConfigPath = "/etc/alr/alr.toml"
|
SystemConfigPath = "/etc/alr/alr.toml"
|
||||||
SystemCachePath = "/var/cache/alr"
|
SystemCachePath = "/var/cache/alr"
|
||||||
TempDir = "/tmp/alr"
|
AlrRunDir = "/var/run/alr"
|
||||||
PrivilegedGroup = "wheel"
|
PrivilegedGroup = "wheel"
|
||||||
)
|
)
|
||||||
|
@@ -21,10 +21,7 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
@@ -57,21 +54,6 @@ func New(config Config) *Database {
|
|||||||
|
|
||||||
func (d *Database) Connect() error {
|
func (d *Database) Connect() error {
|
||||||
dsn := d.config.GetPaths().DBPath
|
dsn := d.config.GetPaths().DBPath
|
||||||
|
|
||||||
// Проверяем директорию для БД
|
|
||||||
dbDir := filepath.Dir(dsn)
|
|
||||||
if _, err := os.Stat(dbDir); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
// Директория не существует - пытаемся создать
|
|
||||||
if mkErr := os.MkdirAll(dbDir, 0775); mkErr != nil {
|
|
||||||
// Не смогли создать - вернём ошибку, пользователь должен использовать alr fix
|
|
||||||
return fmt.Errorf("cache directory does not exist, please run 'alr fix' to create it: %w", mkErr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("failed to check database directory: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
engine, err := xorm.NewEngine("sqlite", dsn)
|
engine, err := xorm.NewEngine("sqlite", dsn)
|
||||||
// engine.SetLogLevel(log.LOG_DEBUG)
|
// engine.SetLogLevel(log.LOG_DEBUG)
|
||||||
// engine.ShowSQL(true)
|
// engine.ShowSQL(true)
|
||||||
|
@@ -1,663 +0,0 @@
|
|||||||
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
|
|
||||||
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
|
|
||||||
//
|
|
||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Встраиваем шаблон для AUR пакетов
|
|
||||||
//
|
|
||||||
//go:embed tmpls/aur.tmpl.sh
|
|
||||||
var aurTmpl string
|
|
||||||
|
|
||||||
// AUROptions содержит параметры для генерации шаблона AUR
|
|
||||||
type AUROptions struct {
|
|
||||||
Name string // Имя пакета в AUR
|
|
||||||
Version string // Версия пакета (опционально, если не указана - берется последняя)
|
|
||||||
CreateDir bool // Создавать ли директорию для пакета и дополнительные файлы
|
|
||||||
}
|
|
||||||
|
|
||||||
// aurAPIResponse представляет структуру ответа от API AUR
|
|
||||||
type aurAPIResponse struct {
|
|
||||||
Version int `json:"version"` // Версия API
|
|
||||||
Type string `json:"type"` // Тип ответа
|
|
||||||
ResultCount int `json:"resultcount"` // Количество результатов
|
|
||||||
Results []aurResult `json:"results"` // Массив результатов
|
|
||||||
Error string `json:"error"` // Сообщение об ошибке (если есть)
|
|
||||||
}
|
|
||||||
|
|
||||||
// aurResult содержит информацию о пакете из AUR
|
|
||||||
type aurResult struct {
|
|
||||||
ID int `json:"ID"`
|
|
||||||
Name string `json:"Name"`
|
|
||||||
PackageBaseID int `json:"PackageBaseID"`
|
|
||||||
PackageBase string `json:"PackageBase"`
|
|
||||||
Version string `json:"Version"`
|
|
||||||
Description string `json:"Description"`
|
|
||||||
URL string `json:"URL"`
|
|
||||||
NumVotes int `json:"NumVotes"`
|
|
||||||
Popularity float64 `json:"Popularity"`
|
|
||||||
OutOfDate *int `json:"OutOfDate"`
|
|
||||||
Maintainer string `json:"Maintainer"`
|
|
||||||
FirstSubmitted int `json:"FirstSubmitted"`
|
|
||||||
LastModified int `json:"LastModified"`
|
|
||||||
URLPath string `json:"URLPath"`
|
|
||||||
License []string `json:"License"`
|
|
||||||
Keywords []string `json:"Keywords"`
|
|
||||||
Depends []string `json:"Depends"`
|
|
||||||
MakeDepends []string `json:"MakeDepends"`
|
|
||||||
OptDepends []string `json:"OptDepends"`
|
|
||||||
CheckDepends []string `json:"CheckDepends"`
|
|
||||||
Conflicts []string `json:"Conflicts"`
|
|
||||||
Provides []string `json:"Provides"`
|
|
||||||
Replaces []string `json:"Replaces"`
|
|
||||||
// Дополнительные поля для данных из PKGBUILD
|
|
||||||
Sources []string `json:"-"`
|
|
||||||
Checksums []string `json:"-"`
|
|
||||||
BuildFunc string `json:"-"`
|
|
||||||
PackageFunc string `json:"-"`
|
|
||||||
PrepareFunc string `json:"-"`
|
|
||||||
PackageType string `json:"-"` // python, go, rust, cpp, nodejs, bin, git
|
|
||||||
HasDesktop bool `json:"-"` // Есть ли desktop файлы
|
|
||||||
HasSystemd bool `json:"-"` // Есть ли systemd сервисы
|
|
||||||
HasVersion bool `json:"-"` // Есть ли функция version()
|
|
||||||
HasScripts []string `json:"-"` // Дополнительные скрипты (postinstall, postremove, etc)
|
|
||||||
HasPatches bool `json:"-"` // Есть ли патчи
|
|
||||||
Architectures []string `json:"-"` // Поддерживаемые архитектуры
|
|
||||||
|
|
||||||
// Автоматически определяемые файлы для install-* команд
|
|
||||||
BinaryFiles []string `json:"-"` // Исполняемые файлы для install-binary
|
|
||||||
LicenseFiles []string `json:"-"` // Лицензионные файлы для install-license
|
|
||||||
ManualFiles []string `json:"-"` // Man страницы для install-manual
|
|
||||||
DesktopFiles []string `json:"-"` // Desktop файлы для install-desktop
|
|
||||||
ServiceFiles []string `json:"-"` // Systemd сервисы для install-systemd
|
|
||||||
CompletionFiles map[string]string `json:"-"` // Файлы автодополнения по типу (bash, zsh, fish)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вспомогательные методы для шаблона
|
|
||||||
func (r aurResult) LicenseString() string {
|
|
||||||
if len(r.License) == 0 {
|
|
||||||
return "custom:Unknown"
|
|
||||||
}
|
|
||||||
// Форматируем лицензии для alr.sh
|
|
||||||
licenses := make([]string, len(r.License))
|
|
||||||
for i, l := range r.License {
|
|
||||||
licenses[i] = fmt.Sprintf("'%s'", l)
|
|
||||||
}
|
|
||||||
return strings.Join(licenses, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r aurResult) DependsString() string {
|
|
||||||
if len(r.Depends) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
deps := make([]string, len(r.Depends))
|
|
||||||
for i, d := range r.Depends {
|
|
||||||
// Убираем версионные ограничения для простоты
|
|
||||||
dep := strings.Split(d, ">=")[0]
|
|
||||||
dep = strings.Split(dep, "<=")[0]
|
|
||||||
dep = strings.Split(dep, "=")[0]
|
|
||||||
dep = strings.Split(dep, ">")[0]
|
|
||||||
dep = strings.Split(dep, "<")[0]
|
|
||||||
deps[i] = fmt.Sprintf("'%s'", dep)
|
|
||||||
}
|
|
||||||
return strings.Join(deps, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r aurResult) MakeDependsString() string {
|
|
||||||
if len(r.MakeDepends) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
deps := make([]string, len(r.MakeDepends))
|
|
||||||
for i, d := range r.MakeDepends {
|
|
||||||
// Убираем версионные ограничения для простоты
|
|
||||||
dep := strings.Split(d, ">=")[0]
|
|
||||||
dep = strings.Split(dep, "<=")[0]
|
|
||||||
dep = strings.Split(dep, "=")[0]
|
|
||||||
dep = strings.Split(dep, ">")[0]
|
|
||||||
dep = strings.Split(dep, "<")[0]
|
|
||||||
deps[i] = fmt.Sprintf("'%s'", dep)
|
|
||||||
}
|
|
||||||
return strings.Join(deps, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r aurResult) GitURL() string {
|
|
||||||
// Формируем URL для клонирования из AUR
|
|
||||||
return fmt.Sprintf("https://aur.archlinux.org/%s.git", r.PackageBase)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r aurResult) ArchitecturesString() string {
|
|
||||||
if len(r.Architectures) == 0 {
|
|
||||||
return "'all'"
|
|
||||||
}
|
|
||||||
archs := make([]string, len(r.Architectures))
|
|
||||||
for i, arch := range r.Architectures {
|
|
||||||
archs[i] = fmt.Sprintf("'%s'", arch)
|
|
||||||
}
|
|
||||||
return strings.Join(archs, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r aurResult) OptDependsString() string {
|
|
||||||
if len(r.OptDepends) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
optDeps := make([]string, 0, len(r.OptDepends))
|
|
||||||
for _, dep := range r.OptDepends {
|
|
||||||
// Форматируем опциональные зависимости для alr.sh
|
|
||||||
parts := strings.SplitN(dep, ": ", 2)
|
|
||||||
if len(parts) == 2 {
|
|
||||||
optDeps = append(optDeps, fmt.Sprintf("'%s: %s'", parts[0], parts[1]))
|
|
||||||
} else {
|
|
||||||
optDeps = append(optDeps, fmt.Sprintf("'%s'", dep))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(optDeps, "\n\t")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r aurResult) ScriptsString() string {
|
|
||||||
if len(r.HasScripts) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
scripts := make([]string, len(r.HasScripts))
|
|
||||||
for i, script := range r.HasScripts {
|
|
||||||
scripts[i] = fmt.Sprintf("['%s']='%s.sh'", script, script)
|
|
||||||
}
|
|
||||||
return strings.Join(scripts, "\n\t")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateInstallCommands генерирует команды install-* для шаблона
|
|
||||||
func (r aurResult) GenerateInstallCommands() string {
|
|
||||||
var commands []string
|
|
||||||
|
|
||||||
// install-binary команды
|
|
||||||
for _, binary := range r.BinaryFiles {
|
|
||||||
if binary == "./"+r.Name {
|
|
||||||
commands = append(commands, fmt.Sprintf("\tinstall-binary %s", binary))
|
|
||||||
} else {
|
|
||||||
commands = append(commands, fmt.Sprintf("\tinstall-binary %s %s", binary, r.Name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// install-license команды
|
|
||||||
for _, license := range r.LicenseFiles {
|
|
||||||
commands = append(commands, fmt.Sprintf("\tinstall-license %s %s/LICENSE", license, r.Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// install-manual команды
|
|
||||||
for _, manual := range r.ManualFiles {
|
|
||||||
commands = append(commands, fmt.Sprintf("\tinstall-manual %s", manual))
|
|
||||||
}
|
|
||||||
|
|
||||||
// install-desktop команды
|
|
||||||
for _, desktop := range r.DesktopFiles {
|
|
||||||
commands = append(commands, fmt.Sprintf("\tinstall-desktop %s", desktop))
|
|
||||||
}
|
|
||||||
|
|
||||||
// install-systemd команды
|
|
||||||
for _, service := range r.ServiceFiles {
|
|
||||||
if strings.Contains(service, "user") {
|
|
||||||
commands = append(commands, fmt.Sprintf("\tinstall-systemd-user %s", service))
|
|
||||||
} else {
|
|
||||||
commands = append(commands, fmt.Sprintf("\tinstall-systemd %s", service))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// install-completion команды
|
|
||||||
for shell, file := range r.CompletionFiles {
|
|
||||||
switch shell {
|
|
||||||
case "bash":
|
|
||||||
commands = append(commands, fmt.Sprintf("\tinstall-completion bash %s < %s", r.Name, file))
|
|
||||||
case "zsh":
|
|
||||||
commands = append(commands, fmt.Sprintf("\tinstall-completion zsh %s < %s", r.Name, file))
|
|
||||||
case "fish":
|
|
||||||
commands = append(commands, fmt.Sprintf("\t%s completion fish | install-completion fish %s", r.Name, r.Name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(commands) == 0 {
|
|
||||||
return "\t# TODO: Добавьте команды установки файлов"
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(commands, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchPKGBUILD загружает PKGBUILD файл для пакета
|
|
||||||
func fetchPKGBUILD(packageBase string) (string, error) {
|
|
||||||
// URL для raw PKGBUILD
|
|
||||||
pkgbuildURL := fmt.Sprintf("https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=%s", packageBase)
|
|
||||||
|
|
||||||
res, err := http.Get(pkgbuildURL)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to fetch PKGBUILD: %w", err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
return "", fmt.Errorf("failed to fetch PKGBUILD: status %s", res.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := io.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to read PKGBUILD: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseSources извлекает источники из PKGBUILD
|
|
||||||
func parseSources(pkgbuild string) []string {
|
|
||||||
var sources []string
|
|
||||||
|
|
||||||
// Регулярное выражение для поиска массива source
|
|
||||||
// Поддерживает как однострочные, так и многострочные определения
|
|
||||||
sourceRegex := regexp.MustCompile(`(?ms)source=\((.*?)\)`)
|
|
||||||
matches := sourceRegex.FindStringSubmatch(pkgbuild)
|
|
||||||
|
|
||||||
if len(matches) > 1 {
|
|
||||||
// Извлекаем содержимое массива source
|
|
||||||
sourceContent := matches[1]
|
|
||||||
|
|
||||||
// Разбираем элементы массива
|
|
||||||
// Учитываем кавычки и переносы строк
|
|
||||||
elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`)
|
|
||||||
elements := elemRegex.FindAllStringSubmatch(sourceContent, -1)
|
|
||||||
|
|
||||||
for _, elem := range elements {
|
|
||||||
if len(elem) > 1 {
|
|
||||||
source := elem[1]
|
|
||||||
// Заменяем переменные версии
|
|
||||||
source = strings.ReplaceAll(source, "$pkgver", "${version}")
|
|
||||||
source = strings.ReplaceAll(source, "${pkgver}", "${version}")
|
|
||||||
source = strings.ReplaceAll(source, "$pkgname", "${name}")
|
|
||||||
source = strings.ReplaceAll(source, "${pkgname}", "${name}")
|
|
||||||
// Обрабатываем другие переменные (упрощенно)
|
|
||||||
source = strings.ReplaceAll(source, "$_commit", "${_commit}")
|
|
||||||
sources = append(sources, source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если источники не найдены в source=(), проверяем source_x86_64 и другие архитектуры
|
|
||||||
if len(sources) == 0 {
|
|
||||||
archSourceRegex := regexp.MustCompile(`(?ms)source_(?:x86_64|aarch64)=\((.*?)\)`)
|
|
||||||
matches = archSourceRegex.FindStringSubmatch(pkgbuild)
|
|
||||||
if len(matches) > 1 {
|
|
||||||
sourceContent := matches[1]
|
|
||||||
elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`)
|
|
||||||
elements := elemRegex.FindAllStringSubmatch(sourceContent, -1)
|
|
||||||
|
|
||||||
for _, elem := range elements {
|
|
||||||
if len(elem) > 1 {
|
|
||||||
source := elem[1]
|
|
||||||
source = strings.ReplaceAll(source, "$pkgver", "${version}")
|
|
||||||
source = strings.ReplaceAll(source, "${pkgver}", "${version}")
|
|
||||||
source = strings.ReplaceAll(source, "$pkgname", "${name}")
|
|
||||||
source = strings.ReplaceAll(source, "${pkgname}", "${name}")
|
|
||||||
sources = append(sources, source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sources
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseChecksums извлекает контрольные суммы из PKGBUILD
|
|
||||||
func parseChecksums(pkgbuild string) []string {
|
|
||||||
var checksums []string
|
|
||||||
|
|
||||||
// Пробуем разные типы контрольных сумм
|
|
||||||
for _, hashType := range []string{"sha256sums", "sha512sums", "sha1sums", "md5sums", "b2sums"} {
|
|
||||||
regex := regexp.MustCompile(fmt.Sprintf(`(?ms)%s=\((.*?)\)`, hashType))
|
|
||||||
matches := regex.FindStringSubmatch(pkgbuild)
|
|
||||||
|
|
||||||
if len(matches) > 1 {
|
|
||||||
content := matches[1]
|
|
||||||
elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`)
|
|
||||||
elements := elemRegex.FindAllStringSubmatch(content, -1)
|
|
||||||
|
|
||||||
for _, elem := range elements {
|
|
||||||
if len(elem) > 1 {
|
|
||||||
checksums = append(checksums, elem[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(checksums) > 0 {
|
|
||||||
break // Используем первый найденный тип хешей
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return checksums
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseFunctions извлекает функции build(), package() и prepare() из PKGBUILD
|
|
||||||
func parseFunctions(pkgbuild string) (buildFunc, packageFunc, prepareFunc string) {
|
|
||||||
// Извлекаем функцию build()
|
|
||||||
buildRegex := regexp.MustCompile(`(?ms)^build\(\)\s*\{(.*?)^\}`)
|
|
||||||
if matches := buildRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 {
|
|
||||||
buildFunc = strings.TrimSpace(matches[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Извлекаем функцию package()
|
|
||||||
packageRegex := regexp.MustCompile(`(?ms)^package\(\)\s*\{(.*?)^\}`)
|
|
||||||
if matches := packageRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 {
|
|
||||||
packageFunc = strings.TrimSpace(matches[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Извлекаем функцию prepare()
|
|
||||||
prepareRegex := regexp.MustCompile(`(?ms)^prepare\(\)\s*\{(.*?)^\}`)
|
|
||||||
if matches := prepareRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 {
|
|
||||||
prepareFunc = strings.TrimSpace(matches[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildFunc, packageFunc, prepareFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// detectInstallableFiles анализирует PKGBUILD и определяет файлы для install-* команд
|
|
||||||
func detectInstallableFiles(pkg *aurResult, pkgbuild string) {
|
|
||||||
// Инициализируем карту для файлов автодополнения
|
|
||||||
pkg.CompletionFiles = make(map[string]string)
|
|
||||||
|
|
||||||
// Для простоты, добавляем стандартные файлы для типа пакета
|
|
||||||
switch pkg.PackageType {
|
|
||||||
case "go":
|
|
||||||
pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name)
|
|
||||||
case "rust":
|
|
||||||
pkg.BinaryFiles = append(pkg.BinaryFiles, "./target/release/"+pkg.Name)
|
|
||||||
case "cpp", "meson":
|
|
||||||
pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name) // обычно в корне после сборки
|
|
||||||
case "bin":
|
|
||||||
pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name)
|
|
||||||
default:
|
|
||||||
if pkg.PackageType != "python" && pkg.PackageType != "nodejs" {
|
|
||||||
pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ищем лицензионные файлы для install-license с более точными паттернами
|
|
||||||
licenseRegex := regexp.MustCompile(`(?i)\b(LICENSE|COPYING|COPYRIGHT|LICENCE)(?:\.[a-zA-Z0-9]+)?\b`)
|
|
||||||
licenseMatches := licenseRegex.FindAllString(pkgbuild, -1)
|
|
||||||
for _, match := range licenseMatches {
|
|
||||||
// Фильтруем только реальные файлы лицензий
|
|
||||||
if strings.Contains(strings.ToLower(match), "license") ||
|
|
||||||
strings.Contains(strings.ToLower(match), "copying") ||
|
|
||||||
strings.Contains(strings.ToLower(match), "copyright") {
|
|
||||||
if !contains(pkg.LicenseFiles, "./"+match) {
|
|
||||||
pkg.LicenseFiles = append(pkg.LicenseFiles, "./"+match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если не найдены лицензионные файлы, добавляем стандартные
|
|
||||||
if len(pkg.LicenseFiles) == 0 {
|
|
||||||
pkg.LicenseFiles = append(pkg.LicenseFiles, "LICENSE")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ищем man страницы для install-manual с более точными паттернами
|
|
||||||
manRegex := regexp.MustCompile(`\b\w+\.(?:1|2|3|4|5|6|7|8)(?:\.gz)?\b`)
|
|
||||||
manMatches := manRegex.FindAllString(pkgbuild, -1)
|
|
||||||
for _, match := range manMatches {
|
|
||||||
// Проверяем, что это не переменная или часть кода
|
|
||||||
if !strings.Contains(match, "$") && !strings.Contains(match, "{") {
|
|
||||||
if !contains(pkg.ManualFiles, "./"+match) {
|
|
||||||
pkg.ManualFiles = append(pkg.ManualFiles, "./"+match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ищем desktop файлы для install-desktop
|
|
||||||
desktopRegex := regexp.MustCompile(`[^/\s]*\.desktop`)
|
|
||||||
desktopMatches := desktopRegex.FindAllString(pkgbuild, -1)
|
|
||||||
for _, match := range desktopMatches {
|
|
||||||
if !contains(pkg.DesktopFiles, "./"+match) {
|
|
||||||
pkg.DesktopFiles = append(pkg.DesktopFiles, "./"+match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ищем systemd сервисы для install-systemd
|
|
||||||
serviceRegex := regexp.MustCompile(`[^/\s]*\.service`)
|
|
||||||
serviceMatches := serviceRegex.FindAllString(pkgbuild, -1)
|
|
||||||
for _, match := range serviceMatches {
|
|
||||||
if !contains(pkg.ServiceFiles, "./"+match) {
|
|
||||||
pkg.ServiceFiles = append(pkg.ServiceFiles, "./"+match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ищем файлы автодополнения
|
|
||||||
completionPatterns := map[string]string{
|
|
||||||
"bash": `completions?/.*\.bash|bash-completion`,
|
|
||||||
"zsh": `completions?/.*\.zsh|zsh.*completion`,
|
|
||||||
"fish": `completions?/.*\.fish|fish.*completion`,
|
|
||||||
}
|
|
||||||
|
|
||||||
for shell, pattern := range completionPatterns {
|
|
||||||
regex := regexp.MustCompile(fmt.Sprintf(`(?i)%s`, pattern))
|
|
||||||
matches := regex.FindAllString(pkgbuild, -1)
|
|
||||||
if len(matches) > 0 {
|
|
||||||
pkg.CompletionFiles[shell] = matches[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// contains проверяет, содержит ли слайс строк указанную строку
|
|
||||||
func contains(slice []string, item string) bool {
|
|
||||||
for _, s := range slice {
|
|
||||||
if s == item {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// detectPackageType определяет тип пакета на основе имени, зависимостей и источников
|
|
||||||
func detectPackageType(pkg *aurResult, pkgbuild string) {
|
|
||||||
name := strings.ToLower(pkg.Name)
|
|
||||||
|
|
||||||
// Определяем тип на основе имени пакета
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(name, "python") || strings.HasPrefix(name, "python3-"):
|
|
||||||
pkg.PackageType = "python"
|
|
||||||
case strings.Contains(name, "nodejs") || strings.Contains(name, "node-"):
|
|
||||||
pkg.PackageType = "nodejs"
|
|
||||||
case strings.HasSuffix(name, "-bin"):
|
|
||||||
pkg.PackageType = "bin"
|
|
||||||
case strings.HasSuffix(name, "-git"):
|
|
||||||
pkg.PackageType = "git"
|
|
||||||
pkg.HasVersion = true // Git пакеты обычно имеют функцию version()
|
|
||||||
case strings.Contains(name, "rust") || hasRustSources(pkg.Sources):
|
|
||||||
pkg.PackageType = "rust"
|
|
||||||
case strings.Contains(name, "go-") || hasGoSources(pkg.Sources):
|
|
||||||
pkg.PackageType = "go"
|
|
||||||
case strings.Contains(name, "-rust") || strings.Contains(name, "paru") || strings.Contains(name, "cargo-"):
|
|
||||||
pkg.PackageType = "rust"
|
|
||||||
default:
|
|
||||||
// Определяем по зависимостям сборки
|
|
||||||
for _, dep := range pkg.MakeDepends {
|
|
||||||
depLower := strings.ToLower(dep)
|
|
||||||
switch {
|
|
||||||
case strings.Contains(depLower, "meson") || strings.Contains(depLower, "ninja"):
|
|
||||||
pkg.PackageType = "meson"
|
|
||||||
case strings.Contains(depLower, "cmake") || strings.Contains(depLower, "gcc") || strings.Contains(depLower, "clang"):
|
|
||||||
pkg.PackageType = "cpp"
|
|
||||||
case strings.Contains(depLower, "python"):
|
|
||||||
pkg.PackageType = "python"
|
|
||||||
case strings.Contains(depLower, "go"):
|
|
||||||
pkg.PackageType = "go"
|
|
||||||
case strings.Contains(depLower, "rust") || strings.Contains(depLower, "cargo"):
|
|
||||||
pkg.PackageType = "rust"
|
|
||||||
case strings.Contains(depLower, "npm") || strings.Contains(depLower, "nodejs"):
|
|
||||||
pkg.PackageType = "nodejs"
|
|
||||||
}
|
|
||||||
if pkg.PackageType != "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Определяем архитектуры на основе типа пакета
|
|
||||||
if pkg.PackageType == "bin" {
|
|
||||||
pkg.Architectures = []string{"amd64"} // Бинарные пакеты обычно специфичны для архитектуры
|
|
||||||
} else {
|
|
||||||
pkg.Architectures = []string{"all"} // Исходный код собирается для любой архитектуры
|
|
||||||
}
|
|
||||||
|
|
||||||
// Определяем наличие desktop файлов
|
|
||||||
pkg.HasDesktop = strings.Contains(pkgbuild, ".desktop") ||
|
|
||||||
strings.Contains(pkgbuild, "install-desktop") ||
|
|
||||||
strings.Contains(pkgbuild, "xdg-desktop")
|
|
||||||
|
|
||||||
// Определяем наличие systemd сервисов
|
|
||||||
pkg.HasSystemd = strings.Contains(pkgbuild, ".service") ||
|
|
||||||
strings.Contains(pkgbuild, "systemctl") ||
|
|
||||||
strings.Contains(pkgbuild, "install-systemd")
|
|
||||||
|
|
||||||
// Определяем наличие функции version() для -git пакетов
|
|
||||||
pkg.HasVersion = strings.Contains(pkgbuild, "pkgver()") ||
|
|
||||||
(strings.HasSuffix(name, "-git") && strings.Contains(pkgbuild, "git describe"))
|
|
||||||
|
|
||||||
// Определяем наличие патчей
|
|
||||||
pkg.HasPatches = strings.Contains(pkgbuild, "patch ") ||
|
|
||||||
strings.Contains(pkgbuild, ".patch") ||
|
|
||||||
strings.Contains(pkgbuild, ".diff")
|
|
||||||
|
|
||||||
// Определяем дополнительные скрипты
|
|
||||||
if strings.Contains(pkgbuild, "post_install") {
|
|
||||||
pkg.HasScripts = append(pkg.HasScripts, "postinstall")
|
|
||||||
}
|
|
||||||
if strings.Contains(pkgbuild, "pre_remove") || strings.Contains(pkgbuild, "post_remove") {
|
|
||||||
pkg.HasScripts = append(pkg.HasScripts, "postremove")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasRustSources проверяет, содержат ли источники Rust проекты
|
|
||||||
func hasRustSources(sources []string) bool {
|
|
||||||
for _, src := range sources {
|
|
||||||
if strings.Contains(src, "crates.io") || strings.Contains(src, "Cargo.toml") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasGoSources проверяет, содержат ли источники Go проекты
|
|
||||||
func hasGoSources(sources []string) bool {
|
|
||||||
for _, src := range sources {
|
|
||||||
if strings.Contains(src, "github.com") && strings.Contains(src, "/go") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// AUR генерирует шаблон alr.sh на основе пакета из AUR
|
|
||||||
func AUR(w io.Writer, opts AUROptions) error {
|
|
||||||
// Создаем шаблон с функциями
|
|
||||||
tmpl, err := template.New("aur").
|
|
||||||
Funcs(funcs).
|
|
||||||
Parse(aurTmpl)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Формируем URL запроса к AUR API
|
|
||||||
apiURL := "https://aur.archlinux.org/rpc/v5/info"
|
|
||||||
params := url.Values{}
|
|
||||||
params.Add("arg[]", opts.Name)
|
|
||||||
fullURL := fmt.Sprintf("%s?%s", apiURL, params.Encode())
|
|
||||||
|
|
||||||
// Выполняем запрос к AUR API
|
|
||||||
res, err := http.Get(fullURL)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to fetch AUR package info: %w", err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
return fmt.Errorf("AUR API returned status: %s", res.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Декодируем ответ
|
|
||||||
var resp aurAPIResponse
|
|
||||||
err = json.NewDecoder(res.Body).Decode(&resp)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to decode AUR response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем наличие ошибки в ответе
|
|
||||||
if resp.Error != "" {
|
|
||||||
return fmt.Errorf("AUR API error: %s", resp.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем, что пакет найден
|
|
||||||
if resp.ResultCount == 0 {
|
|
||||||
return fmt.Errorf("package '%s' not found in AUR", opts.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Берем первый результат
|
|
||||||
pkg := resp.Results[0]
|
|
||||||
|
|
||||||
// Если указана версия, проверяем соответствие
|
|
||||||
if opts.Version != "" && pkg.Version != opts.Version {
|
|
||||||
// Предупреждаем, но продолжаем с актуальной версией из AUR
|
|
||||||
fmt.Fprintf(w, "# WARNING: Requested version %s, but AUR has %s\n", opts.Version, pkg.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загружаем PKGBUILD для получения источников
|
|
||||||
pkgbuild, err := fetchPKGBUILD(pkg.PackageBase)
|
|
||||||
if err != nil {
|
|
||||||
// Если не удалось загрузить PKGBUILD, используем fallback на AUR репозиторий
|
|
||||||
fmt.Fprintf(w, "# WARNING: Could not fetch PKGBUILD: %v\n", err)
|
|
||||||
fmt.Fprintf(w, "# Using AUR repository as source\n")
|
|
||||||
pkg.Sources = []string{fmt.Sprintf("%s::git+%s", pkg.Name, pkg.GitURL())}
|
|
||||||
pkg.Checksums = []string{"SKIP"}
|
|
||||||
} else {
|
|
||||||
// Извлекаем источники из PKGBUILD
|
|
||||||
pkg.Sources = parseSources(pkgbuild)
|
|
||||||
pkg.Checksums = parseChecksums(pkgbuild)
|
|
||||||
pkg.BuildFunc, pkg.PackageFunc, pkg.PrepareFunc = parseFunctions(pkgbuild)
|
|
||||||
|
|
||||||
// Определяем тип пакета
|
|
||||||
detectPackageType(&pkg, pkgbuild)
|
|
||||||
|
|
||||||
// Определяем файлы для install-* команд
|
|
||||||
detectInstallableFiles(&pkg, pkgbuild)
|
|
||||||
|
|
||||||
// Если источники не найдены, используем fallback
|
|
||||||
if len(pkg.Sources) == 0 {
|
|
||||||
fmt.Fprintf(w, "# WARNING: No sources found in PKGBUILD\n")
|
|
||||||
fmt.Fprintf(w, "# Using AUR repository as source\n")
|
|
||||||
pkg.Sources = []string{fmt.Sprintf("%s::git+%s", pkg.Name, pkg.GitURL())}
|
|
||||||
pkg.Checksums = []string{"SKIP"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Выполняем шаблон
|
|
||||||
return tmpl.Execute(w, pkg)
|
|
||||||
}
|
|
@@ -1,133 +0,0 @@
|
|||||||
# This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
|
|
||||||
# It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
|
|
||||||
#
|
|
||||||
# ALR - Any Linux Repository
|
|
||||||
# Copyright (C) 2025 The ALR Authors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# Generated from AUR package: {{.Name}}
|
|
||||||
# Package type: {{.PackageType}}
|
|
||||||
# AUR votes: {{.NumVotes}} | Popularity: {{printf "%.2f" .Popularity}}
|
|
||||||
# Original maintainer: {{.Maintainer}}
|
|
||||||
# Adapted for ALR by automation
|
|
||||||
|
|
||||||
name='{{.Name}}'
|
|
||||||
version='{{.Version}}'
|
|
||||||
release='1'
|
|
||||||
desc='{{.Description}}'
|
|
||||||
{{if ne .Description ""}}desc_ru='{{.Description}}'{{end}}
|
|
||||||
homepage='{{.URL}}'
|
|
||||||
maintainer="Евгений Храмов <xpamych@yandex.ru> (imported from AUR)"
|
|
||||||
{{if ne .Description ""}}maintainer_ru="Евгений Храмов <xpamych@yandex.ru> (импортирован из AUR)"{{end}}
|
|
||||||
architectures=({{.ArchitecturesString}})
|
|
||||||
license=({{.LicenseString}})
|
|
||||||
{{if .Provides}}provides=({{range .Provides}}'{{.}}' {{end}}){{end}}
|
|
||||||
{{if .Conflicts}}conflicts=({{range .Conflicts}}'{{.}}' {{end}}){{end}}
|
|
||||||
{{if .Replaces}}replaces=({{range .Replaces}}'{{.}}' {{end}}){{end}}
|
|
||||||
|
|
||||||
# Базовые зависимости
|
|
||||||
{{if .DependsString}}deps=({{.DependsString}}){{else}}deps=(){{end}}
|
|
||||||
{{if .MakeDependsString}}build_deps=({{.MakeDependsString}}){{else}}build_deps=(){{end}}
|
|
||||||
|
|
||||||
# Зависимости для конкретных дистрибутивов (адаптируйте под нужды пакета)
|
|
||||||
{{if .DependsString}}deps_arch=({{.DependsString}})
|
|
||||||
deps_debian=({{.DependsString}})
|
|
||||||
deps_altlinux=({{.DependsString}})
|
|
||||||
deps_alpine=({{.DependsString}}){{end}}
|
|
||||||
|
|
||||||
{{if and .MakeDependsString (ne .PackageType "bin")}}# Зависимости сборки для конкретных дистрибутивов
|
|
||||||
build_deps_arch=({{.MakeDependsString}})
|
|
||||||
build_deps_debian=({{.MakeDependsString}})
|
|
||||||
build_deps_altlinux=({{.MakeDependsString}})
|
|
||||||
build_deps_alpine=({{.MakeDependsString}}){{end}}
|
|
||||||
|
|
||||||
{{if .OptDependsString}}# Опциональные зависимости
|
|
||||||
opt_deps=(
|
|
||||||
{{.OptDependsString}}
|
|
||||||
){{end}}
|
|
||||||
|
|
||||||
# Источники из PKGBUILD
|
|
||||||
sources=({{range .Sources}}"{{.}}" {{end}})
|
|
||||||
checksums=({{range .Checksums}}'{{.}}' {{end}})
|
|
||||||
|
|
||||||
{{if .HasVersion}}# Функция версии для Git-пакетов
|
|
||||||
version() {
|
|
||||||
cd "$srcdir/{{.Name}}"
|
|
||||||
git-version
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if .ScriptsString}}# Дополнительные скрипты
|
|
||||||
scripts=(
|
|
||||||
{{.ScriptsString}}
|
|
||||||
){{end}}
|
|
||||||
|
|
||||||
{{if or .PrepareFunc .HasPatches}}prepare() {
|
|
||||||
cd "$srcdir"{{if .PrepareFunc}}
|
|
||||||
# Из PKGBUILD:
|
|
||||||
{{.PrepareFunc}}{{else}}
|
|
||||||
# Применение патчей и подготовка исходников
|
|
||||||
# Раскомментируйте и адаптируйте при необходимости:
|
|
||||||
# patch -p1 < "${scriptdir}/fix.patch"{{end}}
|
|
||||||
}{{else}}# prepare() {
|
|
||||||
# cd "$srcdir"
|
|
||||||
# # Применение патчей и подготовка исходников при необходимости
|
|
||||||
# # patch -p1 < "${scriptdir}/fix.patch"
|
|
||||||
# }{{end}}
|
|
||||||
|
|
||||||
{{if ne .PackageType "bin"}}build() {
|
|
||||||
cd "$srcdir"{{if .BuildFunc}}
|
|
||||||
# Из PKGBUILD:
|
|
||||||
{{.BuildFunc}}{{else}}
|
|
||||||
|
|
||||||
# TODO: Адаптируйте команды сборки под конкретный проект ({{.PackageType}})
|
|
||||||
{{if eq .PackageType "meson"}}# Для Meson проектов:
|
|
||||||
meson setup build --prefix=/usr --buildtype=release
|
|
||||||
ninja -C build -j $(nproc){{else if eq .PackageType "cpp"}}# Для C/C++ проектов:
|
|
||||||
mkdir -p build && cd build
|
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
|
||||||
make -j$(nproc){{else if eq .PackageType "go"}}# Для Go проектов:
|
|
||||||
go build -buildmode=pie -trimpath -ldflags "-s -w" -o {{.Name}}{{else if eq .PackageType "python"}}# Для Python проектов:
|
|
||||||
python -m build --wheel --no-isolation{{else if eq .PackageType "nodejs"}}# Для Node.js проектов:
|
|
||||||
npm ci --production
|
|
||||||
npm run build{{else if eq .PackageType "rust"}}# Для Rust проектов:
|
|
||||||
cargo build --release --locked{{else if eq .PackageType "git"}}# Для Git проектов (обычно исходный код):
|
|
||||||
make -j$(nproc){{else}}# Стандартная сборка:
|
|
||||||
make -j$(nproc){{end}}{{end}}
|
|
||||||
}{{else}}# Бинарный пакет - сборка не требуется{{end}}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
cd "$srcdir"{{if .PackageFunc}}
|
|
||||||
# Из PKGBUILD (адаптировано для ALR):
|
|
||||||
{{.PackageFunc}}
|
|
||||||
|
|
||||||
# Автоматически сгенерированные команды установки:
|
|
||||||
{{.GenerateInstallCommands}}{{else}}
|
|
||||||
|
|
||||||
# TODO: Адаптируйте установку файлов под конкретный проект {{.Name}}
|
|
||||||
{{if eq .PackageType "meson"}}# Для Meson проектов:
|
|
||||||
meson install -C build --destdir="$pkgdir"{{else if eq .PackageType "cpp"}}# Для C/C++ проектов:
|
|
||||||
cd build
|
|
||||||
make DESTDIR="$pkgdir" install{{else if eq .PackageType "go"}}# Для Go проектов:
|
|
||||||
# Исполняемый файл уже собран в корне{{else if eq .PackageType "python"}}# Для Python проектов:
|
|
||||||
pip install --root="$pkgdir/" . --no-deps --disable-pip-version-check{{else if eq .PackageType "nodejs"}}# Для Node.js проектов:
|
|
||||||
npm install -g --prefix="$pkgdir/usr" .{{else if eq .PackageType "rust"}}# Для Rust проектов:
|
|
||||||
# Исполняемый файл в target/release/{{else if eq .PackageType "bin"}}# Бинарный пакет:
|
|
||||||
# Файлы уже распакованы{{else}}# Стандартная установка:
|
|
||||||
make DESTDIR="$pkgdir" install{{end}}
|
|
||||||
|
|
||||||
# Автоматически сгенерированные команды установки:
|
|
||||||
{{.GenerateInstallCommands}}{{end}}
|
|
||||||
}
|
|
@@ -16,30 +16,14 @@
|
|||||||
|
|
||||||
package manager
|
package manager
|
||||||
|
|
||||||
import (
|
import "os/exec"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommonPackageManager struct {
|
type CommonPackageManager struct {
|
||||||
noConfirmArg string
|
noConfirmArg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CommonPackageManager) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
func (m *CommonPackageManager) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
||||||
var cmd *exec.Cmd
|
cmd := exec.Command(mgrCmd)
|
||||||
|
|
||||||
// Проверяем, нужно ли повышение привилегий
|
|
||||||
isRoot := os.Geteuid() == 0
|
|
||||||
isCI := os.Getenv("CI") == "true"
|
|
||||||
|
|
||||||
if !isRoot && !isCI {
|
|
||||||
// Если не root и не в CI, используем sudo
|
|
||||||
cmd = exec.Command("sudo", mgrCmd)
|
|
||||||
} else {
|
|
||||||
// Если root или в CI, запускаем напрямую
|
|
||||||
cmd = exec.Command(mgrCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Args = append(cmd.Args, opts.Args...)
|
cmd.Args = append(cmd.Args, opts.Args...)
|
||||||
cmd.Args = append(cmd.Args, args...)
|
cmd.Args = append(cmd.Args, args...)
|
||||||
|
|
||||||
|
@@ -22,7 +22,6 @@ package overrides
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
@@ -183,18 +182,3 @@ func ReleasePlatformSpecific(release int, info *distro.OSRelease) string {
|
|||||||
|
|
||||||
return fmt.Sprintf("%d", release)
|
return fmt.Sprintf("%d", release)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseReleasePlatformSpecific(s string, info *distro.OSRelease) (int, error) {
|
|
||||||
if info.ID == "altlinux" {
|
|
||||||
if strings.HasPrefix(s, "alt") {
|
|
||||||
return strconv.Atoi(s[3:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.ID == "fedora" || slices.Contains(info.Like, "fedora") {
|
|
||||||
parts := strings.SplitN(s, ".", 2)
|
|
||||||
return strconv.Atoi(parts[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.Atoi(s)
|
|
||||||
}
|
|
||||||
|
@@ -233,8 +233,5 @@ func TestReleasePlatformSpecific(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
assert.Equal(t, tc.expected, overrides.ReleasePlatformSpecific(1, tc.info))
|
assert.Equal(t, tc.expected, overrides.ReleasePlatformSpecific(1, tc.info))
|
||||||
release, err := overrides.ParseReleasePlatformSpecific(tc.expected, tc.info)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, 1, release)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,65 +21,44 @@ package repos
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error) {
|
func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error) {
|
||||||
found := make(map[string][]alrsh.Package)
|
found := map[string][]alrsh.Package{}
|
||||||
var notFound []string
|
notFound := []string(nil)
|
||||||
|
|
||||||
for _, pkgName := range pkgs {
|
for _, pkgName := range pkgs {
|
||||||
if pkgName == "" {
|
if pkgName == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var result []alrsh.Package
|
result, err := rs.db.GetPkgs(ctx, "json_array_contains(provides, ?)", pkgName)
|
||||||
var err error
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case strings.Contains(pkgName, "/"):
|
|
||||||
// repo/pkg
|
|
||||||
parts := strings.SplitN(pkgName, "/", 2)
|
|
||||||
repo := parts[0]
|
|
||||||
name := parts[1]
|
|
||||||
result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo)
|
|
||||||
|
|
||||||
case strings.Contains(pkgName, "+alr-"):
|
|
||||||
// pkg+alr-repo
|
|
||||||
parts := strings.SplitN(pkgName, "+alr-", 2)
|
|
||||||
name := parts[0]
|
|
||||||
repo := parts[1]
|
|
||||||
result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo)
|
|
||||||
|
|
||||||
default:
|
|
||||||
result, err = rs.db.GetPkgs(ctx, "json_array_contains(provides, ?)", pkgName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("FindPkgs: get by provides: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(result) == 0 {
|
|
||||||
result, err = rs.db.GetPkgs(ctx, "basepkg_name = ?", pkgName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("FindPkgs: get by basepkg_name: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(result) == 0 {
|
|
||||||
result, err = rs.db.GetPkgs(ctx, "name LIKE ?", pkgName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("FindPkgs: lookup for %q failed: %w", pkgName, err)
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result) == 0 {
|
added := 0
|
||||||
|
for _, pkg := range result {
|
||||||
|
added++
|
||||||
|
found[pkgName] = append(found[pkgName], pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if added == 0 {
|
||||||
|
result, err := rs.db.GetPkgs(ctx, "name LIKE ?", pkgName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkg := range result {
|
||||||
|
added++
|
||||||
|
found[pkgName] = append(found[pkgName], pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if added == 0 {
|
||||||
notFound = append(notFound, pkgName)
|
notFound = append(notFound, pkgName)
|
||||||
} else {
|
|
||||||
found[pkgName] = result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -68,7 +68,7 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, repo := range repos {
|
for _, repo := range repos {
|
||||||
err := rs.pullRepo(ctx, &repo, false)
|
err := rs.pullRepo(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -77,16 +77,7 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Repos) PullOneAndUpdateFromConfig(ctx context.Context, repo *types.Repo) error {
|
func (rs *Repos) pullRepo(ctx context.Context, repo types.Repo) error {
|
||||||
err := rs.pullRepo(ctx, repo, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *Repos) pullRepo(ctx context.Context, repo *types.Repo, updateRepoFromToml bool) error {
|
|
||||||
urls := []string{repo.URL}
|
urls := []string{repo.URL}
|
||||||
urls = append(urls, repo.Mirrors...)
|
urls = append(urls, repo.Mirrors...)
|
||||||
|
|
||||||
@@ -97,7 +88,7 @@ func (rs *Repos) pullRepo(ctx context.Context, repo *types.Repo, updateRepoFromT
|
|||||||
slog.Info(gotext.Get("Trying mirror"), "repo", repo.Name, "mirror", repoURL)
|
slog.Info(gotext.Get("Trying mirror"), "repo", repo.Name, "mirror", repoURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := rs.pullRepoFromURL(ctx, repoURL, repo, updateRepoFromToml)
|
err := rs.pullRepoFromURL(ctx, repoURL, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
slog.Warn(gotext.Get("Failed to pull from URL"), "repo", repo.Name, "url", repoURL, "error", err)
|
slog.Warn(gotext.Get("Failed to pull from URL"), "repo", repo.Name, "url", repoURL, "error", err)
|
||||||
@@ -158,7 +149,7 @@ func readGitRepo(repoDir, repoUrl string) (*git.Repository, bool, error) {
|
|||||||
return r, true, nil
|
return r, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo *types.Repo, update bool) error {
|
func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo types.Repo) error {
|
||||||
repoURL, err := url.Parse(rawRepoUrl)
|
repoURL, err := url.Parse(rawRepoUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid URL %s: %w", rawRepoUrl, err)
|
return fmt.Errorf("invalid URL %s: %w", rawRepoUrl, err)
|
||||||
@@ -223,12 +214,12 @@ func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo *t
|
|||||||
// empty. In this case, we need to update the DB fully
|
// empty. In this case, we need to update the DB fully
|
||||||
// rather than just incrementally.
|
// rather than just incrementally.
|
||||||
if rs.db.IsEmpty() || freshGit {
|
if rs.db.IsEmpty() || freshGit {
|
||||||
err = rs.processRepoFull(ctx, *repo, repoDir)
|
err = rs.processRepoFull(ctx, repo, repoDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = rs.processRepoChanges(ctx, *repo, r, w, old, new)
|
err = rs.processRepoChanges(ctx, repo, r, w, old, new)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -256,18 +247,6 @@ func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo *t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if update {
|
|
||||||
if repoCfg.Repo.URL != "" {
|
|
||||||
repo.URL = repoCfg.Repo.URL
|
|
||||||
}
|
|
||||||
if repoCfg.Repo.Ref != "" {
|
|
||||||
repo.Ref = repoCfg.Repo.Ref
|
|
||||||
}
|
|
||||||
if len(repoCfg.Repo.Mirrors) > 0 {
|
|
||||||
repo.Mirrors = repoCfg.Repo.Mirrors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,106 +0,0 @@
|
|||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package stats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InstallationData struct {
|
|
||||||
PackageName string `json:"packageName"`
|
|
||||||
Version string `json:"version,omitempty"`
|
|
||||||
InstallType string `json:"installType"` // "install" or "upgrade"
|
|
||||||
UserAgent string `json:"userAgent"`
|
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
apiEndpoints = []string{
|
|
||||||
"https://alr.plemya-x.ru/api/packages/track-install",
|
|
||||||
"http://localhost:3001/api/packages/track-install",
|
|
||||||
}
|
|
||||||
userAgent = "ALR-CLI/1.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
func generateFingerprint(packageName string) string {
|
|
||||||
hostname, _ := os.Hostname()
|
|
||||||
data := fmt.Sprintf("%s_%s_%s", hostname, packageName, time.Now().Format("2006-01-02"))
|
|
||||||
hash := sha256.Sum256([]byte(data))
|
|
||||||
return hex.EncodeToString(hash[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrackInstallation отправляет статистику установки пакета
|
|
||||||
func TrackInstallation(ctx context.Context, packageName string, installType string) {
|
|
||||||
// Запускаем в отдельной горутине, чтобы не блокировать основной процесс
|
|
||||||
go func() {
|
|
||||||
data := InstallationData{
|
|
||||||
PackageName: packageName,
|
|
||||||
InstallType: installType,
|
|
||||||
UserAgent: userAgent,
|
|
||||||
Fingerprint: generateFingerprint(packageName),
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonData, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return // Тихо игнорируем ошибки - статистика не критична
|
|
||||||
}
|
|
||||||
|
|
||||||
// Пробуем отправить запрос к разным endpoint-ам
|
|
||||||
for _, endpoint := range apiEndpoints {
|
|
||||||
if sendRequest(endpoint, jsonData) {
|
|
||||||
return // Если хотя бы один запрос прошёл успешно, выходим
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendRequest(endpoint string, data []byte) bool {
|
|
||||||
client := &http.Client{
|
|
||||||
Timeout: 5 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(data))
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set("User-Agent", userAgent)
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return resp.StatusCode >= 200 && resp.StatusCode < 300
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldTrackPackage проверяет, нужно ли отслеживать установку этого пакета
|
|
||||||
func ShouldTrackPackage(packageName string) bool {
|
|
||||||
// Отслеживаем только alr-bin
|
|
||||||
return strings.Contains(packageName, "alr-bin")
|
|
||||||
}
|
|
@@ -9,52 +9,52 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
#: build.go:41
|
#: build.go:42
|
||||||
msgid "Build a local package"
|
msgid "Build a local package"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build.go:47
|
#: build.go:48
|
||||||
msgid "Path to the build script"
|
msgid "Path to the build script"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build.go:52
|
#: build.go:53
|
||||||
msgid "Specify subpackage in script (for multi package script only)"
|
msgid "Specify subpackage in script (for multi package script only)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build.go:57
|
#: build.go:58
|
||||||
msgid "Name of the package to build and its repo (example: default/go-bin)"
|
msgid "Name of the package to build and its repo (example: default/go-bin)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build.go:62
|
#: build.go:63
|
||||||
msgid ""
|
msgid ""
|
||||||
"Build package from scratch even if there's an already built package available"
|
"Build package from scratch even if there's an already built package available"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build.go:72
|
#: build.go:73
|
||||||
msgid "Error getting working directory"
|
msgid "Error getting working directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build.go:117
|
#: build.go:118
|
||||||
msgid "Cannot get absolute script path"
|
msgid "Cannot get absolute script path"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build.go:143
|
#: build.go:152
|
||||||
msgid "Package not found"
|
msgid "Package not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build.go:156
|
#: build.go:165
|
||||||
msgid "Nothing to build"
|
msgid "Nothing to build"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build.go:213
|
#: build.go:222
|
||||||
msgid "Error building package"
|
msgid "Error building package"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build.go:220
|
#: build.go:229
|
||||||
msgid "Error moving the package"
|
msgid "Error moving the package"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: build.go:224
|
#: build.go:233
|
||||||
msgid "Done"
|
msgid "Done"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -218,23 +218,23 @@ msgstr ""
|
|||||||
msgid "Error removing packages"
|
msgid "Error removing packages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/build/build.go:351
|
#: internal/build/build.go:378
|
||||||
msgid "Building package"
|
msgid "Building package"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/build/build.go:380
|
#: internal/build/build.go:407
|
||||||
msgid "The checksums array must be the same length as sources"
|
msgid "The checksums array must be the same length as sources"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/build/build.go:422
|
#: internal/build/build.go:449
|
||||||
msgid "Downloading sources"
|
msgid "Downloading sources"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/build/build.go:468
|
#: internal/build/build.go:495
|
||||||
msgid "Would you like to remove the build dependencies?"
|
msgid "Would you like to remove the build dependencies?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/build/build.go:546
|
#: internal/build/build.go:571
|
||||||
msgid "Installing dependencies"
|
msgid "Installing dependencies"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -407,27 +407,27 @@ msgstr ""
|
|||||||
msgid "ERROR"
|
msgid "ERROR"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/repos/pull.go:97
|
#: internal/repos/pull.go:88
|
||||||
msgid "Trying mirror"
|
msgid "Trying mirror"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/repos/pull.go:103
|
#: internal/repos/pull.go:94
|
||||||
msgid "Failed to pull from URL"
|
msgid "Failed to pull from URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/repos/pull.go:167
|
#: internal/repos/pull.go:158
|
||||||
msgid "Pulling repository"
|
msgid "Pulling repository"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/repos/pull.go:204
|
#: internal/repos/pull.go:195
|
||||||
msgid "Repository up to date"
|
msgid "Repository up to date"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/repos/pull.go:239
|
#: internal/repos/pull.go:230
|
||||||
msgid "Git repository does not appear to be a valid ALR repo"
|
msgid "Git repository does not appear to be a valid ALR repo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/repos/pull.go:255
|
#: internal/repos/pull.go:246
|
||||||
msgid ""
|
msgid ""
|
||||||
"ALR repo's minimum ALR version is greater than the current version. Try "
|
"ALR repo's minimum ALR version is greater than the current version. Try "
|
||||||
"updating ALR if something doesn't work."
|
"updating ALR if something doesn't work."
|
||||||
@@ -445,34 +445,30 @@ msgstr ""
|
|||||||
msgid "You need to be root to perform this action"
|
msgid "You need to be root to perform this action"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: list.go:45
|
#: list.go:43
|
||||||
msgid "List ALR repo packages"
|
msgid "List ALR repo packages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: list.go:59
|
#: list.go:57
|
||||||
msgid "Format output using a Go template"
|
msgid "Format output using a Go template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: list.go:91
|
#: list.go:89
|
||||||
msgid "Error getting packages for upgrade"
|
msgid "Error getting packages for upgrade"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: list.go:94
|
#: list.go:92
|
||||||
msgid "No packages for upgrade"
|
msgid "No packages for upgrade"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: list.go:104 list.go:201
|
#: list.go:102 list.go:184
|
||||||
msgid "Error parsing format template"
|
msgid "Error parsing format template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: list.go:110 list.go:205
|
#: list.go:108 list.go:188
|
||||||
msgid "Error executing template"
|
msgid "Error executing template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: list.go:164
|
|
||||||
msgid "Failed to parse release"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: main.go:45
|
#: main.go:45
|
||||||
msgid "Print the current ALR version and exit"
|
msgid "Print the current ALR version and exit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -485,11 +481,11 @@ msgstr ""
|
|||||||
msgid "Enable interactive questions and prompts"
|
msgid "Enable interactive questions and prompts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main.go:148
|
#: main.go:147
|
||||||
msgid "Show help"
|
msgid "Show help"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: main.go:152
|
#: main.go:151
|
||||||
msgid "Error while running app"
|
msgid "Error while running app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -521,44 +517,44 @@ msgstr ""
|
|||||||
msgid "Pull all repositories that have changed"
|
msgid "Pull all repositories that have changed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:42
|
#: repo.go:41
|
||||||
msgid "Manage repos"
|
msgid "Manage repos"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:56 repo.go:625
|
#: repo.go:55 repo.go:625
|
||||||
msgid "Remove an existing repository"
|
msgid "Remove an existing repository"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:58 repo.go:521
|
#: repo.go:57 repo.go:521
|
||||||
msgid "<name>"
|
msgid "<name>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:103 repo.go:465 repo.go:568
|
#: repo.go:102 repo.go:465 repo.go:568
|
||||||
msgid "Repo \"%s\" does not exist"
|
msgid "Repo \"%s\" does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:110
|
#: repo.go:109
|
||||||
msgid "Error removing repo directory"
|
msgid "Error removing repo directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:114 repo.go:195 repo.go:253 repo.go:316 repo.go:389 repo.go:504
|
#: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504
|
||||||
#: repo.go:576
|
#: repo.go:576
|
||||||
msgid "Error saving config"
|
msgid "Error saving config"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:133
|
#: repo.go:132
|
||||||
msgid "Error removing packages from database"
|
msgid "Error removing packages from database"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:144 repo.go:595
|
#: repo.go:143 repo.go:595
|
||||||
msgid "Add a new repository"
|
msgid "Add a new repository"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:145 repo.go:270 repo.go:345 repo.go:402
|
#: repo.go:144 repo.go:270 repo.go:345 repo.go:402
|
||||||
msgid "<name> <url>"
|
msgid "<name> <url>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:170
|
#: repo.go:169
|
||||||
msgid "Repo \"%s\" already exists"
|
msgid "Repo \"%s\" already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@@ -5,63 +5,63 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: unnamed project\n"
|
"Project-Id-Version: unnamed project\n"
|
||||||
"PO-Revision-Date: 2025-07-09 20:38+0300\n"
|
"PO-Revision-Date: 2025-06-29 21:05+0300\n"
|
||||||
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
|
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
|
||||||
"Language-Team: Russian\n"
|
"Language-Team: Russian\n"
|
||||||
"Language: ru\n"
|
"Language: ru\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
"X-Generator: Gtranslator 48.0\n"
|
"X-Generator: Gtranslator 48.0\n"
|
||||||
|
|
||||||
#: build.go:41
|
#: build.go:42
|
||||||
msgid "Build a local package"
|
msgid "Build a local package"
|
||||||
msgstr "Сборка локального пакета"
|
msgstr "Сборка локального пакета"
|
||||||
|
|
||||||
#: build.go:47
|
#: build.go:48
|
||||||
msgid "Path to the build script"
|
msgid "Path to the build script"
|
||||||
msgstr "Путь к скрипту сборки"
|
msgstr "Путь к скрипту сборки"
|
||||||
|
|
||||||
#: build.go:52
|
#: build.go:53
|
||||||
msgid "Specify subpackage in script (for multi package script only)"
|
msgid "Specify subpackage in script (for multi package script only)"
|
||||||
msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)"
|
msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)"
|
||||||
|
|
||||||
#: build.go:57
|
#: build.go:58
|
||||||
msgid "Name of the package to build and its repo (example: default/go-bin)"
|
msgid "Name of the package to build and its repo (example: default/go-bin)"
|
||||||
msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)"
|
msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)"
|
||||||
|
|
||||||
#: build.go:62
|
#: build.go:63
|
||||||
msgid ""
|
msgid ""
|
||||||
"Build package from scratch even if there's an already built package available"
|
"Build package from scratch even if there's an already built package available"
|
||||||
msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет"
|
msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет"
|
||||||
|
|
||||||
#: build.go:72
|
#: build.go:73
|
||||||
msgid "Error getting working directory"
|
msgid "Error getting working directory"
|
||||||
msgstr "Ошибка при получении рабочего каталога"
|
msgstr "Ошибка при получении рабочего каталога"
|
||||||
|
|
||||||
#: build.go:117
|
#: build.go:118
|
||||||
msgid "Cannot get absolute script path"
|
msgid "Cannot get absolute script path"
|
||||||
msgstr "Невозможно получить абсолютный путь к скрипту"
|
msgstr "Невозможно получить абсолютный путь к скрипту"
|
||||||
|
|
||||||
#: build.go:143
|
#: build.go:152
|
||||||
msgid "Package not found"
|
msgid "Package not found"
|
||||||
msgstr "Пакет не найден"
|
msgstr "Пакет не найден"
|
||||||
|
|
||||||
#: build.go:156
|
#: build.go:165
|
||||||
msgid "Nothing to build"
|
msgid "Nothing to build"
|
||||||
msgstr "Нечего собирать"
|
msgstr "Нечего собирать"
|
||||||
|
|
||||||
#: build.go:213
|
#: build.go:222
|
||||||
msgid "Error building package"
|
msgid "Error building package"
|
||||||
msgstr "Ошибка при сборке пакета"
|
msgstr "Ошибка при сборке пакета"
|
||||||
|
|
||||||
#: build.go:220
|
#: build.go:229
|
||||||
msgid "Error moving the package"
|
msgid "Error moving the package"
|
||||||
msgstr "Ошибка при перемещении пакета"
|
msgstr "Ошибка при перемещении пакета"
|
||||||
|
|
||||||
#: build.go:224
|
#: build.go:233
|
||||||
msgid "Done"
|
msgid "Done"
|
||||||
msgstr "Сделано"
|
msgstr "Сделано"
|
||||||
|
|
||||||
@@ -225,23 +225,23 @@ msgstr "Для команды remove ожидался хотя бы 1 аргум
|
|||||||
msgid "Error removing packages"
|
msgid "Error removing packages"
|
||||||
msgstr "Ошибка при удалении пакетов"
|
msgstr "Ошибка при удалении пакетов"
|
||||||
|
|
||||||
#: internal/build/build.go:351
|
#: internal/build/build.go:378
|
||||||
msgid "Building package"
|
msgid "Building package"
|
||||||
msgstr "Сборка пакета"
|
msgstr "Сборка пакета"
|
||||||
|
|
||||||
#: internal/build/build.go:380
|
#: internal/build/build.go:407
|
||||||
msgid "The checksums array must be the same length as sources"
|
msgid "The checksums array must be the same length as sources"
|
||||||
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
|
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
|
||||||
|
|
||||||
#: internal/build/build.go:422
|
#: internal/build/build.go:449
|
||||||
msgid "Downloading sources"
|
msgid "Downloading sources"
|
||||||
msgstr "Скачивание источников"
|
msgstr "Скачивание источников"
|
||||||
|
|
||||||
#: internal/build/build.go:468
|
#: internal/build/build.go:495
|
||||||
msgid "Would you like to remove the build dependencies?"
|
msgid "Would you like to remove the build dependencies?"
|
||||||
msgstr "Хотели бы вы удалить зависимости сборки?"
|
msgstr "Хотели бы вы удалить зависимости сборки?"
|
||||||
|
|
||||||
#: internal/build/build.go:546
|
#: internal/build/build.go:571
|
||||||
msgid "Installing dependencies"
|
msgid "Installing dependencies"
|
||||||
msgstr "Установка зависимостей"
|
msgstr "Установка зависимостей"
|
||||||
|
|
||||||
@@ -404,8 +404,8 @@ msgid ""
|
|||||||
"This command is deprecated and would be removed in the future, use \"%s\" "
|
"This command is deprecated and would be removed in the future, use \"%s\" "
|
||||||
"instead!"
|
"instead!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Эта команда устарела и будет удалена в будущем, используйте вместо нее "
|
"Эта команда устарела и будет удалена в будущем, используйте вместо нее \"%s"
|
||||||
"\"%s\"!"
|
"\"!"
|
||||||
|
|
||||||
#: internal/db/db.go:76
|
#: internal/db/db.go:76
|
||||||
msgid "Database version mismatch; resetting"
|
msgid "Database version mismatch; resetting"
|
||||||
@@ -421,27 +421,27 @@ msgstr ""
|
|||||||
msgid "ERROR"
|
msgid "ERROR"
|
||||||
msgstr "ОШИБКА"
|
msgstr "ОШИБКА"
|
||||||
|
|
||||||
#: internal/repos/pull.go:97
|
#: internal/repos/pull.go:88
|
||||||
msgid "Trying mirror"
|
msgid "Trying mirror"
|
||||||
msgstr "Пробую зеркало"
|
msgstr "Пробую зеркало"
|
||||||
|
|
||||||
#: internal/repos/pull.go:103
|
#: internal/repos/pull.go:94
|
||||||
msgid "Failed to pull from URL"
|
msgid "Failed to pull from URL"
|
||||||
msgstr "Не удалось извлечь из URL"
|
msgstr "Не удалось извлечь из URL"
|
||||||
|
|
||||||
#: internal/repos/pull.go:167
|
#: internal/repos/pull.go:158
|
||||||
msgid "Pulling repository"
|
msgid "Pulling repository"
|
||||||
msgstr "Скачивание репозитория"
|
msgstr "Скачивание репозитория"
|
||||||
|
|
||||||
#: internal/repos/pull.go:204
|
#: internal/repos/pull.go:195
|
||||||
msgid "Repository up to date"
|
msgid "Repository up to date"
|
||||||
msgstr "Репозиторий уже обновлён"
|
msgstr "Репозиторий уже обновлён"
|
||||||
|
|
||||||
#: internal/repos/pull.go:239
|
#: internal/repos/pull.go:230
|
||||||
msgid "Git repository does not appear to be a valid ALR repo"
|
msgid "Git repository does not appear to be a valid ALR repo"
|
||||||
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
|
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
|
||||||
|
|
||||||
#: internal/repos/pull.go:255
|
#: internal/repos/pull.go:246
|
||||||
msgid ""
|
msgid ""
|
||||||
"ALR repo's minimum ALR version is greater than the current version. Try "
|
"ALR repo's minimum ALR version is greater than the current version. Try "
|
||||||
"updating ALR if something doesn't work."
|
"updating ALR if something doesn't work."
|
||||||
@@ -461,34 +461,30 @@ msgstr "Вы должны быть членом %s чтобы выполнить
|
|||||||
msgid "You need to be root to perform this action"
|
msgid "You need to be root to perform this action"
|
||||||
msgstr "Вы должны быть root чтобы выполнить это"
|
msgstr "Вы должны быть root чтобы выполнить это"
|
||||||
|
|
||||||
#: list.go:45
|
#: list.go:43
|
||||||
msgid "List ALR repo packages"
|
msgid "List ALR repo packages"
|
||||||
msgstr "Список пакетов репозитория ALR"
|
msgstr "Список пакетов репозитория ALR"
|
||||||
|
|
||||||
#: list.go:59
|
#: list.go:57
|
||||||
msgid "Format output using a Go template"
|
msgid "Format output using a Go template"
|
||||||
msgstr "Формат выходных данных с использованием шаблона Go"
|
msgstr "Формат выходных данных с использованием шаблона Go"
|
||||||
|
|
||||||
#: list.go:91
|
#: list.go:89
|
||||||
msgid "Error getting packages for upgrade"
|
msgid "Error getting packages for upgrade"
|
||||||
msgstr "Ошибка при получении пакетов для обновления"
|
msgstr "Ошибка при получении пакетов для обновления"
|
||||||
|
|
||||||
#: list.go:94
|
#: list.go:92
|
||||||
msgid "No packages for upgrade"
|
msgid "No packages for upgrade"
|
||||||
msgstr "Нет пакетов к обновлению"
|
msgstr "Нет пакетов к обновлению"
|
||||||
|
|
||||||
#: list.go:104 list.go:201
|
#: list.go:102 list.go:184
|
||||||
msgid "Error parsing format template"
|
msgid "Error parsing format template"
|
||||||
msgstr "Ошибка при разборе шаблона"
|
msgstr "Ошибка при разборе шаблона"
|
||||||
|
|
||||||
#: list.go:110 list.go:205
|
#: list.go:108 list.go:188
|
||||||
msgid "Error executing template"
|
msgid "Error executing template"
|
||||||
msgstr "Ошибка при выполнении шаблона"
|
msgstr "Ошибка при выполнении шаблона"
|
||||||
|
|
||||||
#: list.go:164
|
|
||||||
msgid "Failed to parse release"
|
|
||||||
msgstr "Не удалось разобрать релиз"
|
|
||||||
|
|
||||||
#: main.go:45
|
#: main.go:45
|
||||||
msgid "Print the current ALR version and exit"
|
msgid "Print the current ALR version and exit"
|
||||||
msgstr "Показать текущую версию ALR и выйти"
|
msgstr "Показать текущую версию ALR и выйти"
|
||||||
@@ -501,11 +497,11 @@ msgstr "Аргументы, которые будут переданы мене
|
|||||||
msgid "Enable interactive questions and prompts"
|
msgid "Enable interactive questions and prompts"
|
||||||
msgstr "Включение интерактивных вопросов и запросов"
|
msgstr "Включение интерактивных вопросов и запросов"
|
||||||
|
|
||||||
#: main.go:148
|
#: main.go:147
|
||||||
msgid "Show help"
|
msgid "Show help"
|
||||||
msgstr "Показать справку"
|
msgstr "Показать справку"
|
||||||
|
|
||||||
#: main.go:152
|
#: main.go:151
|
||||||
msgid "Error while running app"
|
msgid "Error while running app"
|
||||||
msgstr "Ошибка при запуске приложения"
|
msgstr "Ошибка при запуске приложения"
|
||||||
|
|
||||||
@@ -537,44 +533,44 @@ msgstr "%s %s загружается — %s/с\n"
|
|||||||
msgid "Pull all repositories that have changed"
|
msgid "Pull all repositories that have changed"
|
||||||
msgstr "Скачать все изменённые репозитории"
|
msgstr "Скачать все изменённые репозитории"
|
||||||
|
|
||||||
#: repo.go:42
|
#: repo.go:41
|
||||||
msgid "Manage repos"
|
msgid "Manage repos"
|
||||||
msgstr "Управление репозиториями"
|
msgstr "Управление репозиториями"
|
||||||
|
|
||||||
#: repo.go:56 repo.go:625
|
#: repo.go:55 repo.go:625
|
||||||
msgid "Remove an existing repository"
|
msgid "Remove an existing repository"
|
||||||
msgstr "Удалить существующий репозиторий"
|
msgstr "Удалить существующий репозиторий"
|
||||||
|
|
||||||
#: repo.go:58 repo.go:521
|
#: repo.go:57 repo.go:521
|
||||||
msgid "<name>"
|
msgid "<name>"
|
||||||
msgstr "<имя>"
|
msgstr "<имя>"
|
||||||
|
|
||||||
#: repo.go:103 repo.go:465 repo.go:568
|
#: repo.go:102 repo.go:465 repo.go:568
|
||||||
msgid "Repo \"%s\" does not exist"
|
msgid "Repo \"%s\" does not exist"
|
||||||
msgstr "Репозитория \"%s\" не существует"
|
msgstr "Репозитория \"%s\" не существует"
|
||||||
|
|
||||||
#: repo.go:110
|
#: repo.go:109
|
||||||
msgid "Error removing repo directory"
|
msgid "Error removing repo directory"
|
||||||
msgstr "Ошибка при удалении каталога репозитория"
|
msgstr "Ошибка при удалении каталога репозитория"
|
||||||
|
|
||||||
#: repo.go:114 repo.go:195 repo.go:253 repo.go:316 repo.go:389 repo.go:504
|
#: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504
|
||||||
#: repo.go:576
|
#: repo.go:576
|
||||||
msgid "Error saving config"
|
msgid "Error saving config"
|
||||||
msgstr "Ошибка при сохранении конфигурации"
|
msgstr "Ошибка при сохранении конфигурации"
|
||||||
|
|
||||||
#: repo.go:133
|
#: repo.go:132
|
||||||
msgid "Error removing packages from database"
|
msgid "Error removing packages from database"
|
||||||
msgstr "Ошибка при удалении пакетов из базы данных"
|
msgstr "Ошибка при удалении пакетов из базы данных"
|
||||||
|
|
||||||
#: repo.go:144 repo.go:595
|
#: repo.go:143 repo.go:595
|
||||||
msgid "Add a new repository"
|
msgid "Add a new repository"
|
||||||
msgstr "Добавить новый репозиторий"
|
msgstr "Добавить новый репозиторий"
|
||||||
|
|
||||||
#: repo.go:145 repo.go:270 repo.go:345 repo.go:402
|
#: repo.go:144 repo.go:270 repo.go:345 repo.go:402
|
||||||
msgid "<name> <url>"
|
msgid "<name> <url>"
|
||||||
msgstr "<имя> <url>"
|
msgstr "<имя> <url>"
|
||||||
|
|
||||||
#: repo.go:170
|
#: repo.go:169
|
||||||
msgid "Repo \"%s\" already exists"
|
msgid "Repo \"%s\" already exists"
|
||||||
msgstr "Репозиторий \"%s\" уже существует"
|
msgstr "Репозиторий \"%s\" уже существует"
|
||||||
|
|
||||||
|
@@ -17,9 +17,12 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@@ -29,23 +32,115 @@ import (
|
|||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsNotRoot проверяет, что текущий пользователь не является root
|
func GetUidGidAlrUserString() (string, string, error) {
|
||||||
|
u, err := user.Lookup("alr")
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.Uid, u.Gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUidGidAlrUser() (int, int, error) {
|
||||||
|
strUid, strGid, err := GetUidGidAlrUserString()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := strconv.Atoi(strUid)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
gid, err := strconv.Atoi(strGid)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uid, gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DropCapsToAlrUser() error {
|
||||||
|
uid, gid, err := GetUidGidAlrUser()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = syscall.Setgid(gid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = syscall.Setuid(uid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return EnsureIsAlrUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExitIfCantDropGidToAlr() cli.ExitCoder {
|
||||||
|
_, gid, err := GetUidGidAlrUser()
|
||||||
|
if err != nil {
|
||||||
|
return cliutils.FormatCliExit("cannot get gid alr", err)
|
||||||
|
}
|
||||||
|
err = syscall.Setgid(gid)
|
||||||
|
if err != nil {
|
||||||
|
return cliutils.FormatCliExit("cannot get setgid alr", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitIfCantDropCapsToAlrUser attempts to drop capabilities to the already
|
||||||
|
// running user. Returns a cli.ExitCoder with an error if the operation fails.
|
||||||
|
// See also [ExitIfCantDropCapsToAlrUserNoPrivs] for a version that also applies
|
||||||
|
// no-new-privs.
|
||||||
|
func ExitIfCantDropCapsToAlrUser() cli.ExitCoder {
|
||||||
|
err := DropCapsToAlrUser()
|
||||||
|
if err != nil {
|
||||||
|
return cliutils.FormatCliExit(gotext.Get("Error on dropping capabilities"), err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExitIfCantSetNoNewPrivs() cli.ExitCoder {
|
||||||
|
if err := NoNewPrivs(); err != nil {
|
||||||
|
return cliutils.FormatCliExit("error on NoNewPrivs", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitIfCantDropCapsToAlrUserNoPrivs combines [ExitIfCantDropCapsToAlrUser] with [ExitIfCantSetNoNewPrivs]
|
||||||
|
func ExitIfCantDropCapsToAlrUserNoPrivs() cli.ExitCoder {
|
||||||
|
if err := ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ExitIfCantSetNoNewPrivs(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func IsNotRoot() bool {
|
func IsNotRoot() bool {
|
||||||
return os.Getuid() != 0
|
return os.Getuid() != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnuseIsPrivilegedGroupMember проверяет, что пользователь является членом привилегированной группы (wheel)
|
func EnsureIsAlrUser() error {
|
||||||
|
uid, gid, err := GetUidGidAlrUser()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newUid := syscall.Getuid()
|
||||||
|
if newUid != uid {
|
||||||
|
return errors.New("new uid don't matches requested")
|
||||||
|
}
|
||||||
|
newGid := syscall.Getgid()
|
||||||
|
if newGid != gid {
|
||||||
|
return errors.New("new gid don't matches requested")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func EnuseIsPrivilegedGroupMember() error {
|
func EnuseIsPrivilegedGroupMember() error {
|
||||||
// В CI пропускаем проверку группы wheel
|
|
||||||
if os.Getenv("CI") == "true" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если пользователь root, пропускаем проверку
|
|
||||||
if os.Geteuid() == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
currentUser, err := user.Current()
|
currentUser, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -69,6 +164,26 @@ func EnuseIsPrivilegedGroupMember() error {
|
|||||||
return cliutils.FormatCliExit(gotext.Get("You need to be a %s member to perform this action", constants.PrivilegedGroup), nil)
|
return cliutils.FormatCliExit(gotext.Get("You need to be a %s member to perform this action", constants.PrivilegedGroup), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EscalateToRootGid() error {
|
||||||
|
return syscall.Setgid(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EscalateToRootUid() error {
|
||||||
|
return syscall.Setuid(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EscalateToRoot() error {
|
||||||
|
err := EscalateToRootUid()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = EscalateToRootGid()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func RootNeededAction(f cli.ActionFunc) cli.ActionFunc {
|
func RootNeededAction(f cli.ActionFunc) cli.ActionFunc {
|
||||||
return func(ctx *cli.Context) error {
|
return func(ctx *cli.Context) error {
|
||||||
deps, err := appbuilder.
|
deps, err := appbuilder.
|
||||||
|
@@ -16,78 +16,8 @@
|
|||||||
|
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import "golang.org/x/sys/unix"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NoNewPrivs() error {
|
func NoNewPrivs() error {
|
||||||
return unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
|
return unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureTempDirWithRootOwner создает каталог в /tmp/alr с правами для группы wheel
|
|
||||||
// Все каталоги в /tmp/alr принадлежат root:wheel с правами 775
|
|
||||||
// Для других каталогов использует стандартные права
|
|
||||||
func EnsureTempDirWithRootOwner(path string, mode os.FileMode) error {
|
|
||||||
if strings.HasPrefix(path, "/tmp/alr") {
|
|
||||||
// Сначала создаем директорию обычным способом
|
|
||||||
err := os.MkdirAll(path, mode)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// В CI или если мы уже root, не нужно использовать sudo
|
|
||||||
isRoot := os.Geteuid() == 0
|
|
||||||
isCI := os.Getenv("CI") == "true"
|
|
||||||
|
|
||||||
// В CI создаем директории с обычными правами
|
|
||||||
if isCI {
|
|
||||||
// В CI не используем группу wheel и не меняем права
|
|
||||||
// Устанавливаем базовые права 777 для временных каталогов
|
|
||||||
chmodCmd := exec.Command("chmod", "777", path)
|
|
||||||
chmodCmd.Run() // Игнорируем ошибки
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Для обычной работы устанавливаем права и группу wheel
|
|
||||||
permissions := "2775"
|
|
||||||
group := "wheel"
|
|
||||||
|
|
||||||
var chmodCmd, chownCmd *exec.Cmd
|
|
||||||
if isRoot {
|
|
||||||
// Выполняем команды напрямую без sudo
|
|
||||||
chmodCmd = exec.Command("chmod", permissions, path)
|
|
||||||
chownCmd = exec.Command("chown", "root:"+group, path)
|
|
||||||
} else {
|
|
||||||
// Используем sudo для обычных пользователей
|
|
||||||
chmodCmd = exec.Command("sudo", "chmod", permissions, path)
|
|
||||||
chownCmd = exec.Command("sudo", "chown", "root:"+group, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Устанавливаем права с setgid битом
|
|
||||||
err = chmodCmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
// Для root игнорируем ошибки, если группа wheel не существует
|
|
||||||
if !isRoot {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Устанавливаем владельца root:wheel
|
|
||||||
err = chownCmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
// Для root игнорируем ошибки, если группа wheel не существует
|
|
||||||
if !isRoot {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Для остальных каталогов обычное создание
|
|
||||||
return os.MkdirAll(path, mode)
|
|
||||||
}
|
|
||||||
|
41
list.go
41
list.go
@@ -24,7 +24,6 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
@@ -34,7 +33,7 @@ import (
|
|||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,6 +58,9 @@ func ListCmd() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
|
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := c.Context
|
ctx := c.Context
|
||||||
|
|
||||||
@@ -124,12 +126,7 @@ func ListCmd() *cli.Command {
|
|||||||
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
type verInfo struct {
|
installedAlrPackages := map[string]string{}
|
||||||
Version string
|
|
||||||
Release int
|
|
||||||
}
|
|
||||||
|
|
||||||
installedAlrPackages := map[string]verInfo{}
|
|
||||||
if c.Bool("installed") {
|
if c.Bool("installed") {
|
||||||
mgr := manager.Detect()
|
mgr := manager.Detect()
|
||||||
if mgr == nil {
|
if mgr == nil {
|
||||||
@@ -147,50 +144,40 @@ func ListCmd() *cli.Command {
|
|||||||
if matches != nil {
|
if matches != nil {
|
||||||
packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")]
|
packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")]
|
||||||
repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")]
|
repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")]
|
||||||
|
installedAlrPackages[fmt.Sprintf("%s/%s", repoName, packageName)] = version
|
||||||
verInfo := verInfo{
|
|
||||||
Version: version,
|
|
||||||
Release: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
if i := strings.LastIndex(version, "-"); i != -1 {
|
|
||||||
verInfo.Version = version[:i]
|
|
||||||
verInfo.Release, err = overrides.ParseReleasePlatformSpecific(version[i+1:], info)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error(gotext.Get("Failed to parse release"), "err", err)
|
|
||||||
return cli.Exit(err, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
installedAlrPackages[fmt.Sprintf("%s/%s", repoName, packageName)] = verInfo
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkg := range result {
|
for _, pkg := range result {
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
if slices.Contains(cfg.IgnorePkgUpdates(), pkg.Name) {
|
if slices.Contains(cfg.IgnorePkgUpdates(), pkg.Name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
type packageInfo struct {
|
type packageInfo struct {
|
||||||
Package *alrsh.Package
|
Package *alrsh.Package
|
||||||
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
pkgInfo := &packageInfo{}
|
pkgInfo := &packageInfo{}
|
||||||
pkgInfo.Package = &pkg
|
pkgInfo.Package = &pkg
|
||||||
|
pkgInfo.Version = pkg.Version
|
||||||
if c.Bool("installed") {
|
if c.Bool("installed") {
|
||||||
instVersion, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
|
instVersion, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
pkg.Version = instVersion.Version
|
pkgInfo.Version = instVersion
|
||||||
pkg.Release = instVersion.Release
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
format := c.String("format")
|
format := c.String("format")
|
||||||
if format == "" {
|
if format == "" {
|
||||||
format = "{{.Package.Repository}}/{{.Package.Name}} {{.Package.Version}}-{{.Package.Release}}\n"
|
format = "{{.Package.Repository}}/{{.Package.Name}} {{.Version}}\n"
|
||||||
}
|
}
|
||||||
tmpl, err := template.New("format").Parse(format)
|
tmpl, err := template.New("format").Parse(format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
2
main.go
2
main.go
@@ -87,7 +87,7 @@ func GetApp() *cli.App {
|
|||||||
// Internal commands
|
// Internal commands
|
||||||
InternalBuildCmd(),
|
InternalBuildCmd(),
|
||||||
InternalInstallCmd(),
|
InternalInstallCmd(),
|
||||||
InternalReposCmd(),
|
InternalMountCmd(),
|
||||||
},
|
},
|
||||||
Before: func(c *cli.Context) error {
|
Before: func(c *cli.Context) error {
|
||||||
if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {
|
if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {
|
||||||
|
42
pkg/dl/dl.go
42
pkg/dl/dl.go
@@ -280,14 +280,14 @@ func handleCache(cacheDir, dest, name string, t Type) (bool, error) {
|
|||||||
cd.Close()
|
cd.Close()
|
||||||
|
|
||||||
if slices.Contains(names, name) {
|
if slices.Contains(names, name) {
|
||||||
err = linkOrCopy(filepath.Join(cacheDir, name), dest)
|
err = os.Link(filepath.Join(cacheDir, name), dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
case TypeDir:
|
case TypeDir:
|
||||||
err := linkOrCopyDir(cacheDir, dest)
|
err := linkDir(cacheDir, dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -296,40 +296,8 @@ func handleCache(cacheDir, dest, name string, t Type) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// linkOrCopy пытается создать жесткую ссылку, а если не получается - копирует файл
|
// Функция linkDir рекурсивно создает жесткие ссылки для файлов из каталога src в каталог dest
|
||||||
func linkOrCopy(src, dest string) error {
|
func linkDir(src, dest string) error {
|
||||||
err := os.Link(src, dest)
|
|
||||||
if err != nil {
|
|
||||||
// Если не удалось создать ссылку, копируем файл
|
|
||||||
srcFile, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer srcFile.Close()
|
|
||||||
|
|
||||||
destFile, err := os.Create(dest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer destFile.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(destFile, srcFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Копируем права доступа
|
|
||||||
srcInfo, err := srcFile.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Chmod(dest, srcInfo.Mode())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// linkOrCopyDir рекурсивно создает жесткие ссылки или копирует файлы из каталога src в каталог dest
|
|
||||||
func linkOrCopyDir(src, dest string) error {
|
|
||||||
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -349,7 +317,7 @@ func linkOrCopyDir(src, dest string) error {
|
|||||||
return os.MkdirAll(newPath, info.Mode())
|
return os.MkdirAll(newPath, info.Mode())
|
||||||
}
|
}
|
||||||
|
|
||||||
return linkOrCopy(path, newPath)
|
return os.Link(path, newPath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -22,7 +22,6 @@ package dl
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -150,7 +149,6 @@ func (d *GitDownloader) Update(opts Options) (bool, error) {
|
|||||||
u.Scheme = strings.TrimPrefix(u.Scheme, "git+")
|
u.Scheme = strings.TrimPrefix(u.Scheme, "git+")
|
||||||
|
|
||||||
query := u.Query()
|
query := u.Query()
|
||||||
rev := query.Get("~rev")
|
|
||||||
query.Del("~rev")
|
query.Del("~rev")
|
||||||
|
|
||||||
depthStr := query.Get("~depth")
|
depthStr := query.Get("~depth")
|
||||||
@@ -179,67 +177,27 @@ func (d *GitDownloader) Update(opts Options) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, we do a fetch to get all the revisions.
|
po := &git.PullOptions{
|
||||||
fo := &git.FetchOptions{
|
Depth: depth,
|
||||||
Depth: depth,
|
Progress: opts.Progress,
|
||||||
Progress: opts.Progress,
|
RecurseSubmodules: git.NoRecurseSubmodules,
|
||||||
|
}
|
||||||
|
|
||||||
|
if recursive == "true" {
|
||||||
|
po.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := getManifest(opts.Destination)
|
m, err := getManifest(opts.Destination)
|
||||||
manifestOK := err == nil
|
manifestOK := err == nil
|
||||||
|
|
||||||
err = r.Fetch(fo)
|
err = w.Pull(po)
|
||||||
if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
|
if err != nil {
|
||||||
|
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a revision is specified, switch to it.
|
|
||||||
if rev != "" {
|
|
||||||
// We are trying to find the revision as a hash of the commit
|
|
||||||
hash, err := r.ResolveRevision(plumbing.Revision(rev))
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to resolve revision %s: %w", rev, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.Checkout(&git.CheckoutOptions{
|
|
||||||
Hash: *hash,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to checkout revision %s: %w", rev, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if recursive == "true" {
|
|
||||||
submodules, err := w.Submodules()
|
|
||||||
if err == nil {
|
|
||||||
err = submodules.Update(&git.SubmoduleUpdateOptions{
|
|
||||||
Init: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to update submodules %s: %w", rev, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If the revision is not specified, we do a regular pull.
|
|
||||||
po := &git.PullOptions{
|
|
||||||
Depth: depth,
|
|
||||||
Progress: opts.Progress,
|
|
||||||
RecurseSubmodules: git.NoRecurseSubmodules,
|
|
||||||
}
|
|
||||||
|
|
||||||
if recursive == "true" {
|
|
||||||
po.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.Pull(po)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = VerifyHashFromLocal("", opts)
|
err = VerifyHashFromLocal("", opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@@ -153,7 +153,7 @@ func TestGitDownloaderUpdate(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
updated, err := d.Update(dl.Options{
|
updated, err := d.Update(dl.Options{
|
||||||
URL: "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git?~rev=test-update-git-downloader",
|
URL: "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git~rev=test-update-git-downloader",
|
||||||
Destination: dest,
|
Destination: dest,
|
||||||
Hash: hsh,
|
Hash: hsh,
|
||||||
HashAlgorithm: "sha256",
|
HashAlgorithm: "sha256",
|
||||||
|
@@ -61,8 +61,7 @@ func (dc *DownloadCache) New(ctx context.Context, id string) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем директорию с правильными правами (различается для prod и тестов)
|
err = os.MkdirAll(itemPath, 0o755)
|
||||||
err = createDir(itemPath, 0o2775)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@@ -1,37 +0,0 @@
|
|||||||
//go:build !test
|
|
||||||
|
|
||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// createDir создает директорию с правильными правами для production
|
|
||||||
func createDir(itemPath string, mode os.FileMode) error {
|
|
||||||
// Используем специальную функцию для создания каталогов с setgid битом только для /tmp/alr
|
|
||||||
// В остальных случаях используем обычное создание директории
|
|
||||||
if strings.HasPrefix(itemPath, "/tmp/alr") {
|
|
||||||
return utils.EnsureTempDirWithRootOwner(itemPath, mode)
|
|
||||||
} else {
|
|
||||||
return os.MkdirAll(itemPath, mode)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -45,7 +45,7 @@ func (c *TestALRConfig) GetPaths() *config.Paths {
|
|||||||
func prepare(t *testing.T) *TestALRConfig {
|
func prepare(t *testing.T) *TestALRConfig {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
dir, err := os.MkdirTemp("", "alr-dlcache-test.*")
|
dir, err := os.MkdirTemp("/tmp", "alr-dlcache-test.*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ func prepare(t *testing.T) *TestALRConfig {
|
|||||||
|
|
||||||
func cleanup(t *testing.T, cfg *TestALRConfig) {
|
func cleanup(t *testing.T, cfg *TestALRConfig) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
os.RemoveAll(cfg.CacheDir)
|
os.Remove(cfg.CacheDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
@@ -82,12 +82,6 @@ func TestNew(t *testing.T) {
|
|||||||
fi, err := os.Stat(dir)
|
fi, err := os.Stat(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("stat: expected no error, got %s", err)
|
t.Errorf("stat: expected no error, got %s", err)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi == nil {
|
|
||||||
t.Errorf("Expected file info to not be nil")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !fi.IsDir() {
|
if !fi.IsDir() {
|
||||||
|
@@ -1,28 +0,0 @@
|
|||||||
//go:build test
|
|
||||||
|
|
||||||
// ALR - Any Linux Repository
|
|
||||||
// Copyright (C) 2025 The ALR Authors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// createDir создает директорию с обычными правами для тестирования
|
|
||||||
func createDir(itemPath string, mode os.FileMode) error {
|
|
||||||
return os.MkdirAll(itemPath, mode)
|
|
||||||
}
|
|
@@ -21,14 +21,13 @@ package types
|
|||||||
|
|
||||||
// Config represents the ALR configuration file
|
// Config represents the ALR configuration file
|
||||||
type Config struct {
|
type Config struct {
|
||||||
RootCmd string `json:"rootCmd" koanf:"rootCmd"`
|
RootCmd string `json:"rootCmd" koanf:"rootCmd"`
|
||||||
UseRootCmd bool `json:"useRootCmd" koanf:"useRootCmd"`
|
UseRootCmd bool `json:"useRootCmd" koanf:"useRootCmd"`
|
||||||
PagerStyle string `json:"pagerStyle" koanf:"pagerStyle"`
|
PagerStyle string `json:"pagerStyle" koanf:"pagerStyle"`
|
||||||
IgnorePkgUpdates []string `json:"ignorePkgUpdates" koanf:"ignorePkgUpdates"`
|
IgnorePkgUpdates []string `json:"ignorePkgUpdates" koanf:"ignorePkgUpdates"`
|
||||||
Repos []Repo `json:"repo" koanf:"repo"`
|
Repos []Repo `json:"repo" koanf:"repo"`
|
||||||
AutoPull bool `json:"autoPull" koanf:"autoPull"`
|
AutoPull bool `json:"autoPull" koanf:"autoPull"`
|
||||||
LogLevel string `json:"logLevel" koanf:"logLevel"`
|
LogLevel string `json:"logLevel" koanf:"logLevel"`
|
||||||
UpdateSystemOnUpgrade bool `json:"updateSystemOnUpgrade" koanf:"updateSystemOnUpgrade"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repo represents a ALR repo within a configuration file
|
// Repo represents a ALR repo within a configuration file
|
||||||
|
@@ -22,9 +22,6 @@ package types
|
|||||||
// RepoConfig represents a ALR repo's alr-repo.toml file.
|
// RepoConfig represents a ALR repo's alr-repo.toml file.
|
||||||
type RepoConfig struct {
|
type RepoConfig struct {
|
||||||
Repo struct {
|
Repo struct {
|
||||||
MinVersion string `toml:"minVersion"`
|
MinVersion string `toml:"minVersion"`
|
||||||
URL string `toml:"url"`
|
|
||||||
Ref string `toml:"ref"`
|
|
||||||
Mirrors []string `toml:"mirrors"`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RefreshCmd() *cli.Command {
|
func RefreshCmd() *cli.Command {
|
||||||
@@ -29,6 +30,9 @@ func RefreshCmd() *cli.Command {
|
|||||||
Usage: gotext.Get("Pull all repositories that have changed"),
|
Usage: gotext.Get("Pull all repositories that have changed"),
|
||||||
Aliases: []string{"ref"},
|
Aliases: []string{"ref"},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
|
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := c.Context
|
ctx := c.Context
|
||||||
|
|
||||||
|
37
repo.go
37
repo.go
@@ -29,7 +29,6 @@ import (
|
|||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||||
@@ -114,6 +113,9 @@ func RemoveRepoCmd() *cli.Command {
|
|||||||
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
|
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
deps, err = appbuilder.
|
deps, err = appbuilder.
|
||||||
New(ctx).
|
New(ctx).
|
||||||
@@ -167,24 +169,10 @@ func AddRepoCmd() *cli.Command {
|
|||||||
return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" already exists", repo.Name), nil)
|
return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" already exists", repo.Name), nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
reposSlice = append(reposSlice, types.Repo{
|
||||||
newRepo := types.Repo{
|
|
||||||
Name: name,
|
Name: name,
|
||||||
URL: repoURL,
|
URL: repoURL,
|
||||||
}
|
})
|
||||||
|
|
||||||
r, close, err := build.GetSafeReposExecutor()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer close()
|
|
||||||
|
|
||||||
newRepo, err = r.PullOneAndUpdateFromConfig(c.Context, &newRepo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reposSlice = append(reposSlice, newRepo)
|
|
||||||
cfg.SetRepos(reposSlice)
|
cfg.SetRepos(reposSlice)
|
||||||
|
|
||||||
err = cfg.System.Save()
|
err = cfg.System.Save()
|
||||||
@@ -192,6 +180,21 @@ func AddRepoCmd() *cli.Command {
|
|||||||
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
|
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
deps, err = appbuilder.
|
||||||
|
New(ctx).
|
||||||
|
UseConfig(cfg).
|
||||||
|
WithDB().
|
||||||
|
WithReposForcePull().
|
||||||
|
Build()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer deps.Defer()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@@ -1,37 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# ALR - Any Linux Repository
|
|
||||||
# Copyright (C) 2025 The ALR Authors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Запускаем форматирование
|
|
||||||
make fmt || true
|
|
||||||
|
|
||||||
# Проверяем какие файлы были изменены (только те, что отслеживаются git)
|
|
||||||
CHANGED_FILES=$(git diff --name-only --diff-filter=M | grep '\.go$' || true)
|
|
||||||
|
|
||||||
# Если файлы были изменены, добавляем их в git
|
|
||||||
if [ ! -z "$CHANGED_FILES" ]; then
|
|
||||||
echo "Formatting changed the following files:"
|
|
||||||
echo "$CHANGED_FILES"
|
|
||||||
# Добавляем только измененные файлы, которые уже отслеживаются
|
|
||||||
echo "$CHANGED_FILES" | xargs -r git add
|
|
||||||
echo "Files were formatted and staged"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Formatting completed"
|
|
||||||
# Всегда возвращаем успех
|
|
||||||
exit 0
|
|
@@ -1,63 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# ALR - Any Linux Repository
|
|
||||||
# Copyright (C) 2025 The ALR Authors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# Wrapper script for i18n that automatically stages changed files for pre-commit
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Сохраняем состояние файлов до выполнения i18n
|
|
||||||
TRANSLATION_FILES=(
|
|
||||||
"internal/translations/default.pot"
|
|
||||||
"internal/translations/po/ru/default.po"
|
|
||||||
"assets/i18n-ru-badge.svg"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Создаем временные файлы для сравнения
|
|
||||||
TEMP_DIR=$(mktemp -d)
|
|
||||||
for file in "${TRANSLATION_FILES[@]}"; do
|
|
||||||
if [[ -f "$file" ]]; then
|
|
||||||
cp "$file" "$TEMP_DIR/$(basename "$file")"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Выполняем обновление переводов
|
|
||||||
make i18n
|
|
||||||
|
|
||||||
# Проверяем какие файлы изменились и добавляем их в staging area
|
|
||||||
CHANGED_FILES=()
|
|
||||||
for file in "${TRANSLATION_FILES[@]}"; do
|
|
||||||
if [[ -f "$file" ]]; then
|
|
||||||
if [[ ! -f "$TEMP_DIR/$(basename "$file")" ]] || ! cmp -s "$file" "$TEMP_DIR/$(basename "$file")"; then
|
|
||||||
CHANGED_FILES+=("$file")
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Добавляем измененные файлы в git staging area
|
|
||||||
if [[ ${#CHANGED_FILES[@]} -gt 0 ]]; then
|
|
||||||
echo "Auto-staging changed translation files:"
|
|
||||||
for file in "${CHANGED_FILES[@]}"; do
|
|
||||||
echo " - $file"
|
|
||||||
git add "$file"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Очищаем временные файлы
|
|
||||||
rm -rf "$TEMP_DIR"
|
|
||||||
|
|
||||||
# Выход с кодом 0 (успех) даже если файлы были изменены
|
|
||||||
exit 0
|
|
@@ -32,20 +32,12 @@ error() {
|
|||||||
|
|
||||||
installPkg() {
|
installPkg() {
|
||||||
rootCmd=""
|
rootCmd=""
|
||||||
|
if command -v doas &>/dev/null; then
|
||||||
# Проверяем, запущен ли скрипт от root
|
rootCmd="doas"
|
||||||
if [ "$(id -u)" = "0" ]; then
|
elif command -v sudo &>/dev/null; then
|
||||||
# Если root, не используем sudo/doas
|
rootCmd="sudo"
|
||||||
rootCmd=""
|
|
||||||
else
|
else
|
||||||
# Если не root, ищем команду повышения привилегий
|
warn "Не обнаружена команда повышения привилегий (например, sudo, doas)"
|
||||||
if command -v doas &>/dev/null; then
|
|
||||||
rootCmd="doas"
|
|
||||||
elif command -v sudo &>/dev/null; then
|
|
||||||
rootCmd="sudo"
|
|
||||||
else
|
|
||||||
warn "Не обнаружена команда повышения привилегий (например, sudo, doas)"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case $1 in
|
case $1 in
|
||||||
@@ -56,46 +48,10 @@ installPkg() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
trackInstallation() {
|
|
||||||
# Отправить статистику установки (не критично если не получится)
|
|
||||||
if command -v curl &>/dev/null; then
|
|
||||||
# Генерируем уникальный отпечаток на основе hostname и даты
|
|
||||||
fingerprint=$(echo "$(hostname)_$(date +%Y-%m-%d)" | sha256sum 2>/dev/null | cut -d' ' -f1 || echo "$(hostname)_$(date +%Y-%m-%d)")
|
|
||||||
|
|
||||||
# Пробуем разные домены/порты для отправки статистики
|
|
||||||
for api_url in "https://alr.plemya-x.ru/api/packages/track-install" "http://localhost:3001/api/packages/track-install"; do
|
|
||||||
curl -s -m 5 -X POST "$api_url" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "User-Agent: ALR-InstallScript/1.0" \
|
|
||||||
-d "{
|
|
||||||
\"packageName\": \"alr-bin\",
|
|
||||||
\"installType\": \"script\",
|
|
||||||
\"userAgent\": \"ALR-InstallScript/1.0\",
|
|
||||||
\"fingerprint\": \"$fingerprint\"
|
|
||||||
}" >/dev/null 2>&1
|
|
||||||
# Если один запрос удался, не пробуем остальные
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! command -v curl &>/dev/null; then
|
if ! command -v curl &>/dev/null; then
|
||||||
error "Этот скрипт требует команду curl. Пожалуйста, установите её и запустите снова."
|
error "Этот скрипт требует команду curl. Пожалуйста, установите её и запустите снова."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Определение архитектуры системы
|
|
||||||
arch=$(uname -m)
|
|
||||||
case $arch in
|
|
||||||
x86_64) debArch="amd64"; rpmArch="x86_64" ;;
|
|
||||||
aarch64) debArch="arm64"; rpmArch="aarch64" ;;
|
|
||||||
armv7l) debArch="armhf"; rpmArch="armv7hl" ;;
|
|
||||||
*) error "Неподдерживаемая архитектура: $arch" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
info "Обнаружена архитектура: $arch"
|
|
||||||
|
|
||||||
pkgFormat=""
|
pkgFormat=""
|
||||||
pkgMgr=""
|
pkgMgr=""
|
||||||
if command -v pacman &>/dev/null; then
|
if command -v pacman &>/dev/null; then
|
||||||
@@ -132,50 +88,25 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$noPkgMgr" ]; then
|
if [ -z "$noPkgMgr" ]; then
|
||||||
info "Получение списка релизов через API Gitea"
|
info "Получение списка файлов с https://gitea.plemya-x.ru/Plemya-x/ALR/releases"
|
||||||
|
|
||||||
# Используем API для получения последнего релиза
|
# Изменено URL и регулярное выражение для списка файлов
|
||||||
releases=$(curl -s "https://gitea.plemya-x.ru/api/v1/repos/Plemya-x/ALR/releases")
|
pageContent=$(curl -s https://gitea.plemya-x.ru/Plemya-x/ALR/releases)
|
||||||
|
|
||||||
if [ -z "$releases" ] || [ "$releases" = "null" ]; then
|
# Извлечение списка файлов из HTML
|
||||||
error "Не удалось получить список релизов. Проверьте соединение с интернетом."
|
fileList=$(echo "$pageContent" | grep -oP '(?<=href=").*?(?=")' | grep -E 'alr-bin.*\.(pkg.tar.zst|rpm|deb)')
|
||||||
fi
|
|
||||||
|
|
||||||
# Получаем URL последнего релиза
|
echo "Полученный список файлов:"
|
||||||
latestReleaseUrl=$(echo "$releases" | grep -o '"browser_download_url":"[^"]*"' | head -1 | cut -d'"' -f4)
|
echo "$fileList"
|
||||||
|
|
||||||
if [ -z "$latestReleaseUrl" ]; then
|
|
||||||
# Fallback на парсинг HTML если API не работает
|
|
||||||
warn "API не доступен, пробуем получить список через HTML"
|
|
||||||
pageContent=$(curl -s https://gitea.plemya-x.ru/Plemya-x/ALR/releases)
|
|
||||||
fileList=$(echo "$pageContent" | grep -oP '(?<=href=")[^"]*alr-bin[^"]*\.(pkg\.tar\.zst|rpm|deb)' | sed 's|^|https://gitea.plemya-x.ru|')
|
|
||||||
else
|
|
||||||
# Получаем список файлов из API
|
|
||||||
latestReleaseId=$(echo "$releases" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
|
|
||||||
assets=$(curl -s "https://gitea.plemya-x.ru/api/v1/repos/Plemya-x/ALR/releases/$latestReleaseId/assets")
|
|
||||||
# Фильтруем только пакеты, исключая tar.gz архивы
|
|
||||||
fileList=$(echo "$assets" | grep -o '"browser_download_url":"[^"]*"' | cut -d'"' -f4 | grep -v '\.tar\.gz$')
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$fileList" ]; then
|
|
||||||
warn "Не найдены готовые пакеты в последнем релизе"
|
|
||||||
warn "Возможно, для вашего дистрибутива нужно собрать пакет из исходников"
|
|
||||||
warn "Инструкции по сборке: https://gitea.plemya-x.ru/Plemya-x/ALR"
|
|
||||||
error "Не удалось получить список пакетов для загрузки"
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Получен список файлов релиза"
|
|
||||||
|
|
||||||
if [ "$pkgMgr" == "pacman" ]; then
|
if [ "$pkgMgr" == "pacman" ]; then
|
||||||
latestFile=$(echo "$fileList" | grep -E "alr-bin.*\.pkg\.tar\.zst" | sort -V | tail -n 1)
|
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.pkg\.tar\.zst' | sort -V | tail -n 1)
|
||||||
elif [ "$pkgMgr" == "apt" ]; then
|
elif [ "$pkgMgr" == "apt" ]; then
|
||||||
latestFile=$(echo "$fileList" | grep -E "alr-bin.*\.${debArch}\.deb" | sort -V | tail -n 1)
|
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.amd64\.deb' | sort -V | tail -n 1)
|
||||||
elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then
|
elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then
|
||||||
latestFile=$(echo "$fileList" | grep -E "alr-bin.*\.${rpmArch}\.rpm" | grep -v 'alt[0-9]*' | sort -V | tail -n 1)
|
latestFile=$(printf "%s\n" "${fileList[@]}" | grep -E 'alr-bin-.*\.x86_64\.rpm' | grep -v 'alt[0-9]*' | sort -V | tail -n 1)
|
||||||
elif [ "$pkgMgr" == "apt-get" ]; then
|
elif [ "$pkgMgr" == "apt-get" ]; then
|
||||||
latestFile=$(echo "$fileList" | grep -E "alr-bin.*-alt[0-9]+\.${rpmArch}\.rpm" | sort -V | tail -n 1)
|
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*-alt[0-9]+\.x86_64\.rpm' | sort -V | tail -n 1)
|
||||||
elif [ "$pkgMgr" == "apk" ]; then
|
|
||||||
latestFile=$(echo "$fileList" | grep -E "alr-bin.*\.apk" | sort -V | tail -n 1)
|
|
||||||
else
|
else
|
||||||
error "Не поддерживаемый менеджер пакетов для автоматической установки"
|
error "Не поддерживаемый менеджер пакетов для автоматической установки"
|
||||||
fi
|
fi
|
||||||
@@ -188,35 +119,18 @@ if [ -z "$noPkgMgr" ]; then
|
|||||||
|
|
||||||
fname="$(mktemp -u -p /tmp "alr.XXXXXXXXXX").${pkgFormat}"
|
fname="$(mktemp -u -p /tmp "alr.XXXXXXXXXX").${pkgFormat}"
|
||||||
|
|
||||||
# Настраиваем trap для очистки временного файла
|
|
||||||
trap "rm -f $fname" EXIT
|
|
||||||
|
|
||||||
info "Загрузка пакета ALR"
|
info "Загрузка пакета ALR"
|
||||||
info "URL: $latestFile"
|
curl -o $fname -L "$latestFile"
|
||||||
|
|
||||||
# Загружаем с проверкой кода возврата
|
if [ ! -f "$fname" ]; then
|
||||||
if ! curl -f -L -o "$fname" "$latestFile"; then
|
error "Ошибка загрузки пакета ALR"
|
||||||
error "Ошибка загрузки пакета ALR. Проверьте подключение к интернету."
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Проверяем что файл не пустой
|
|
||||||
if [ ! -s "$fname" ]; then
|
|
||||||
error "Загруженный файл пустой или поврежден"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Показываем размер загруженного файла
|
|
||||||
fileSize=$(du -h "$fname" | cut -f1)
|
|
||||||
info "Загружен пакет размером $fileSize"
|
|
||||||
|
|
||||||
info "Установка пакета ALR"
|
info "Установка пакета ALR"
|
||||||
installPkg "$pkgMgr" "$fname"
|
installPkg "$pkgMgr" "$fname"
|
||||||
|
|
||||||
# Отправляем статистику установки
|
|
||||||
trackInstallation
|
|
||||||
|
|
||||||
info "Очистка"
|
info "Очистка"
|
||||||
rm -f "$fname"
|
rm "$fname"
|
||||||
trap - EXIT
|
|
||||||
|
|
||||||
info "Готово!"
|
info "Готово!"
|
||||||
else
|
else
|
||||||
|
@@ -1,38 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# ALR - Any Linux Repository
|
|
||||||
# Copyright (C) 2025 The ALR Authors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Запускаем тесты с покрытием
|
|
||||||
make test-coverage
|
|
||||||
|
|
||||||
# coverage.out в .gitignore, не добавляем его
|
|
||||||
# Но если скрипт coverage-badge.sh изменил какие-то файлы (например, README с бейджем),
|
|
||||||
# они будут добавлены
|
|
||||||
CHANGED_FILES=$(git diff --name-only --diff-filter=M | grep -v '\.out$' | grep -v '^coverage' || true)
|
|
||||||
|
|
||||||
if [ ! -z "$CHANGED_FILES" ]; then
|
|
||||||
echo "Test coverage updated the following files:"
|
|
||||||
echo "$CHANGED_FILES"
|
|
||||||
# Добавляем только измененные файлы, которые уже отслеживаются
|
|
||||||
echo "$CHANGED_FILES" | xargs -r git add
|
|
||||||
echo "Files were updated and staged"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Tests completed successfully"
|
|
||||||
# Всегда возвращаем успех если тесты прошли
|
|
||||||
exit 0
|
|
@@ -29,6 +29,7 @@ import (
|
|||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/search"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/search"
|
||||||
|
"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/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
)
|
)
|
||||||
@@ -71,6 +72,9 @@ func SearchCmd() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
|
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ctx := c.Context
|
ctx := c.Context
|
||||||
|
|
||||||
|
19
upgrade.go
19
upgrade.go
@@ -55,6 +55,9 @@ func UpgradeCmd() *cli.Command {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: utils.RootNeededAction(func(c *cli.Context) error {
|
Action: utils.RootNeededAction(func(c *cli.Context) error {
|
||||||
|
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
installer, installerClose, err := build.GetSafeInstaller()
|
installer, installerClose, err := build.GetSafeInstaller()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -62,6 +65,9 @@ func UpgradeCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
defer installerClose()
|
defer installerClose()
|
||||||
|
|
||||||
|
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
scripter, scripterClose, err := build.GetSafeScriptExecutor()
|
scripter, scripterClose, err := build.GetSafeScriptExecutor()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -84,19 +90,6 @@ func UpgradeCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
defer deps.Defer()
|
defer deps.Defer()
|
||||||
|
|
||||||
// Обновляем систему, если это включено в конфигурации
|
|
||||||
if deps.Cfg.UpdateSystemOnUpgrade() {
|
|
||||||
slog.Info(gotext.Get("Updating system packages..."))
|
|
||||||
err = deps.Manager.UpgradeAll(&manager.Opts{
|
|
||||||
NoConfirm: !c.Bool("interactive"),
|
|
||||||
Args: manager.Args,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error updating system packages"), err)
|
|
||||||
}
|
|
||||||
slog.Info(gotext.Get("System packages updated successfully"))
|
|
||||||
}
|
|
||||||
|
|
||||||
builder, err := build.NewMainBuilder(
|
builder, err := build.NewMainBuilder(
|
||||||
deps.Cfg,
|
deps.Cfg,
|
||||||
deps.Manager,
|
deps.Manager,
|
||||||
|
Reference in New Issue
Block a user