Добавление статистики
Исправление работы с мультипакетами
This commit is contained in:
@@ -32,6 +32,7 @@ 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"
|
||||||
@@ -401,9 +402,19 @@ 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,6 +539,13 @@ 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
|
||||||
@@ -552,11 +570,13 @@ func (b *Builder) BuildALRDeps(
|
|||||||
repoDeps = notFound
|
repoDeps = notFound
|
||||||
|
|
||||||
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
|
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
|
||||||
pkgs := cliutils.FlattenPkgs(
|
// Для зависимостей указываем isDependency = true
|
||||||
|
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
|
||||||
@@ -691,6 +711,13 @@ func (i *Builder) InstallPkgs(
|
|||||||
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 {
|
||||||
@@ -700,6 +727,13 @@ func (i *Builder) InstallPkgs(
|
|||||||
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
|
||||||
|
@@ -103,22 +103,62 @@ 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 && interactive {
|
if len(pkgs) > 1 {
|
||||||
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
|
// Проверяем, являются ли пакеты подпакетами одного мультипакета
|
||||||
if err != nil {
|
if isMultiPackage(pkgs) && verb == "install" {
|
||||||
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])
|
||||||
}
|
}
|
||||||
outPkgs = append(outPkgs, choice)
|
} else {
|
||||||
} 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 {
|
||||||
|
@@ -60,6 +60,13 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrs
|
|||||||
return nil, nil, fmt.Errorf("FindPkgs: get by provides: %w", err)
|
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 {
|
if len(result) == 0 {
|
||||||
result, err = rs.db.GetPkgs(ctx, "name LIKE ?", pkgName)
|
result, err = rs.db.GetPkgs(ctx, "name LIKE ?", pkgName)
|
||||||
}
|
}
|
||||||
|
106
internal/stats/tracker.go
Normal file
106
internal/stats/tracker.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
// ALR - Any Linux Repository
|
||||||
|
// Copyright (C) 2025 The ALR Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// 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")
|
||||||
|
}
|
@@ -56,6 +56,31 @@ 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
|
||||||
@@ -186,6 +211,9 @@ if [ -z "$noPkgMgr" ]; then
|
|||||||
info "Установка пакета ALR"
|
info "Установка пакета ALR"
|
||||||
installPkg "$pkgMgr" "$fname"
|
installPkg "$pkgMgr" "$fname"
|
||||||
|
|
||||||
|
# Отправляем статистику установки
|
||||||
|
trackInstallation
|
||||||
|
|
||||||
info "Очистка"
|
info "Очистка"
|
||||||
rm -f "$fname"
|
rm -f "$fname"
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
|
Reference in New Issue
Block a user