Добавление статистики
Исправление работы с мультипакетами
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/config" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/stats" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||
| @@ -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. | ||||
| 	var filteredDepends []string | ||||
| 	 | ||||
| 	// Создаем набор подпакетов текущего мультипакета для исключения циклических зависимостей | ||||
| 	currentPackageNames := make(map[string]struct{}) | ||||
| 	for _, pkg := range input.packages { | ||||
| 		currentPackageNames[pkg] = struct{}{} | ||||
| 	} | ||||
| 	 | ||||
| 	for _, d := range depends { | ||||
| 		if _, found := depNames[d]; !found { | ||||
| 			filteredDepends = append(filteredDepends, d) | ||||
| 			// Исключаем зависимости, которые являются подпакетами текущего мультипакета | ||||
| 			if _, isCurrentPackage := currentPackageNames[d]; !isCurrentPackage { | ||||
| 				filteredDepends = append(filteredDepends, d) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -528,6 +539,13 @@ func (b *Builder) InstallALRPackages( | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		 | ||||
| 		// Отслеживание установки ALR пакетов | ||||
| 		for _, dep := range res { | ||||
| 			if stats.ShouldTrackPackage(dep.Name) { | ||||
| 				stats.TrackInstallation(ctx, dep.Name, "upgrade") | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| @@ -552,11 +570,13 @@ func (b *Builder) BuildALRDeps( | ||||
| 		repoDeps = notFound | ||||
|  | ||||
| 		// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез | ||||
| 		pkgs := cliutils.FlattenPkgs( | ||||
| 		// Для зависимостей указываем isDependency = true | ||||
| 		pkgs := cliutils.FlattenPkgsWithContext( | ||||
| 			ctx, | ||||
| 			found, | ||||
| 			"install", | ||||
| 			input.BuildOpts().Interactive, | ||||
| 			true, | ||||
| 		) | ||||
| 		type item struct { | ||||
| 			pkg      *alrsh.Package | ||||
| @@ -691,6 +711,13 @@ func (i *Builder) InstallPkgs( | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		 | ||||
| 		// Отслеживание установки локальных пакетов | ||||
| 		for _, dep := range builtDeps { | ||||
| 			if stats.ShouldTrackPackage(dep.Name) { | ||||
| 				stats.TrackInstallation(ctx, dep.Name, "install") | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(repoDeps) > 0 { | ||||
| @@ -700,6 +727,13 @@ func (i *Builder) InstallPkgs( | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		 | ||||
| 		// Отслеживание установки пакетов из репозитория | ||||
| 		for _, pkg := range repoDeps { | ||||
| 			if stats.ShouldTrackPackage(pkg) { | ||||
| 				stats.TrackInstallation(ctx, pkg, "install") | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| // 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 { | ||||
| 	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 | ||||
| 	for _, pkgs := range found { | ||||
| 		if len(pkgs) > 1 && interactive { | ||||
| 			choice, err := PkgPrompt(ctx, pkgs, verb, interactive) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error prompting for choice of package")) | ||||
| 				os.Exit(1) | ||||
| 		if len(pkgs) > 1 { | ||||
| 			// Проверяем, являются ли пакеты подпакетами одного мультипакета | ||||
| 			if isMultiPackage(pkgs) && verb == "install" { | ||||
| 				// Для мультипакетов при установке ВСЕГДА берем все подпакеты без выбора | ||||
| 				// Это правильное поведение как для прямой установки, так и для зависимостей | ||||
| 				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 if len(pkgs) == 1 || !interactive { | ||||
| 		} else { | ||||
| 			// Если только один пакет - берем его | ||||
| 			outPkgs = append(outPkgs, pkgs[0]) | ||||
| 		} | ||||
| 	} | ||||
| 	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. | ||||
| func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, interactive bool) (alrsh.Package, error) { | ||||
| 	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) | ||||
| 			} | ||||
|  | ||||
| 			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) | ||||
| 			} | ||||
|   | ||||
							
								
								
									
										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 | ||||
| } | ||||
|  | ||||
| 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 | ||||
|   error "Этот скрипт требует команду curl. Пожалуйста, установите её и запустите снова." | ||||
| fi | ||||
| @@ -186,6 +211,9 @@ if [ -z "$noPkgMgr" ]; then | ||||
|   info "Установка пакета ALR" | ||||
|   installPkg "$pkgMgr" "$fname" | ||||
|  | ||||
|   # Отправляем статистику установки | ||||
|   trackInstallation | ||||
|  | ||||
|   info "Очистка" | ||||
|   rm -f "$fname" | ||||
|   trap - EXIT | ||||
|   | ||||
		Reference in New Issue
	
	Block a user