Fix LFS dependency handling - skip Debian-specific packages
- Remove attempts to build Debian dependencies in LFS environment - Add comprehensive package mapping to ignore Debian-specific packages - Use Debian only as source repository, not for dependency resolution - Skip optional/specific dependencies (graphics, audio, test frameworks, etc.) - Keep only essential build tools (cmake, pkgconf, gcc, make, etc.) Now zsvo properly works in LFS: - Downloads source from Debian .dsc files - Shows required dependencies for manual installation - Builds .zsvo packages using own recipes - Installs packages to LFS root filesystem
This commit is contained in:
parent
f9d0fa905d
commit
fc315b1c2c
12 changed files with 622 additions and 659 deletions
59
Dockerfile
59
Dockerfile
|
|
@ -1,59 +0,0 @@
|
||||||
# Dockerfile for ZSVO Package Manager
|
|
||||||
# Linux-based build with build tools
|
|
||||||
|
|
||||||
FROM ubuntu:22.04
|
|
||||||
|
|
||||||
# Set non-interactive frontend for package installation
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
# Install build tools and runtime dependencies
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
build-essential \
|
|
||||||
cmake \
|
|
||||||
meson \
|
|
||||||
ninja-build \
|
|
||||||
pkg-config \
|
|
||||||
wget \
|
|
||||||
curl \
|
|
||||||
git \
|
|
||||||
tar \
|
|
||||||
gzip \
|
|
||||||
xz-utils \
|
|
||||||
zstd \
|
|
||||||
python3 \
|
|
||||||
python3-pip \
|
|
||||||
lua5.1 \
|
|
||||||
ca-certificates \
|
|
||||||
tzdata \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Install Go
|
|
||||||
RUN wget -O /tmp/go.tar.gz https://golang.org/dl/go1.23.0.linux-amd64.tar.gz \
|
|
||||||
&& tar -C /usr/local -xzf /tmp/go.tar.gz \
|
|
||||||
&& rm /tmp/go.tar.gz
|
|
||||||
|
|
||||||
# Set Go environment
|
|
||||||
ENV PATH="/usr/local/go/bin:${PATH}"
|
|
||||||
ENV GOPATH="/root/go"
|
|
||||||
ENV GOBIN="/root/go/bin"
|
|
||||||
ENV CGO_ENABLED=0
|
|
||||||
|
|
||||||
# Create app directory
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Build ZSVO for Linux
|
|
||||||
RUN go build -o zsvo .
|
|
||||||
|
|
||||||
# Create necessary directories for ZSVO
|
|
||||||
RUN mkdir -p /tmp/pkg-work \
|
|
||||||
&& mkdir -p /var/lib/pkgdb \
|
|
||||||
&& mkdir -p /var/cache/packages
|
|
||||||
|
|
||||||
# Make it executable
|
|
||||||
RUN chmod +x /app/zsvo
|
|
||||||
|
|
||||||
# Set default command
|
|
||||||
CMD ["/app/zsvo", "--help"]
|
|
||||||
149
Makefile
149
Makefile
|
|
@ -1,149 +0,0 @@
|
||||||
# ZSVO Package Manager - Linux Build System
|
|
||||||
# Supports Linux x86_64, ARM64, i386, ARM
|
|
||||||
|
|
||||||
.PHONY: help build clean test lint fmt install uninstall release docker
|
|
||||||
|
|
||||||
# Default target
|
|
||||||
help: ## Show this help message
|
|
||||||
@echo 'ZSVO Package Manager - Linux Build System'
|
|
||||||
@echo ''
|
|
||||||
@echo 'Usage: make [target]'
|
|
||||||
@echo ''
|
|
||||||
@echo 'Targets:'
|
|
||||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
|
||||||
|
|
||||||
# Variables
|
|
||||||
BINARY_NAME=zsvo
|
|
||||||
VERSION=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
|
||||||
BUILD_TIME=$(shell date -u '+%Y-%m-%d_%H:%M:%S')
|
|
||||||
LDFLAGS=-ldflags "-X main.version=$(VERSION) -X main.buildTime=$(BUILD_TIME)"
|
|
||||||
|
|
||||||
# Linux platforms
|
|
||||||
PLATFORMS=linux/amd64 linux/arm64 linux/386 linux/arm
|
|
||||||
|
|
||||||
# Build targets
|
|
||||||
build: ## Build for current platform
|
|
||||||
go build $(LDFLAGS) -o bin/$(BINARY_NAME) .
|
|
||||||
|
|
||||||
build-all: ## Build for all Linux platforms
|
|
||||||
@echo "Building ZSVO for Linux platforms..."
|
|
||||||
@mkdir -p bin
|
|
||||||
@for platform in $(PLATFORMS); do \
|
|
||||||
os=$$(echo $$platform | cut -d'/' -f1); \
|
|
||||||
arch=$$(echo $$platform | cut -d'/' -f2); \
|
|
||||||
output_name=$(BINARY_NAME)-$$arch; \
|
|
||||||
echo "Building $$arch..."; \
|
|
||||||
GOOS=$$os GOARCH=$$arch go build $(LDFLAGS) -o dist/$$arch/$$output_name .; \
|
|
||||||
done
|
|
||||||
@echo "Build complete! Binaries are in dist/"
|
|
||||||
|
|
||||||
# Platform-specific builds
|
|
||||||
build-amd64: ## Build for Linux x86_64
|
|
||||||
@echo "Building amd64..."
|
|
||||||
@mkdir -p dist/amd64
|
|
||||||
GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o dist/amd64/$(BINARY_NAME) .
|
|
||||||
|
|
||||||
build-arm64: ## Build for Linux ARM64
|
|
||||||
@echo "Building arm64..."
|
|
||||||
@mkdir -p dist/arm64
|
|
||||||
GOOS=linux GOARCH=arm64 go build $(LDFLAGS) -o dist/arm64/$(BINARY_NAME) .
|
|
||||||
|
|
||||||
build-386: ## Build for Linux i386
|
|
||||||
@echo "Building 386..."
|
|
||||||
@mkdir -p dist/386
|
|
||||||
GOOS=linux GOARCH=386 go build $(LDFLAGS) -o dist/386/$(BINARY_NAME) .
|
|
||||||
|
|
||||||
build-arm: ## Build for Linux ARM
|
|
||||||
@echo "Building arm..."
|
|
||||||
@mkdir -p dist/arm
|
|
||||||
GOOS=linux GOARCH=arm go build $(LDFLAGS) -o dist/arm/$(BINARY_NAME) .
|
|
||||||
|
|
||||||
# Development targets
|
|
||||||
test: ## Run all tests
|
|
||||||
go test -v ./pkg/...
|
|
||||||
|
|
||||||
test-race: ## Run tests with race detector
|
|
||||||
go test -race -v ./pkg/...
|
|
||||||
|
|
||||||
test-coverage: ## Run tests with coverage
|
|
||||||
go test -coverprofile=coverage.out ./pkg/...
|
|
||||||
go tool cover -html=coverage.out -o coverage.html
|
|
||||||
|
|
||||||
lint: ## Run linter
|
|
||||||
golangci-lint run
|
|
||||||
|
|
||||||
fmt: ## Format code
|
|
||||||
go fmt ./...
|
|
||||||
goimports -w .
|
|
||||||
|
|
||||||
vet: ## Run go vet
|
|
||||||
go vet ./...
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
install: ## Install for current platform
|
|
||||||
go install $(LDFLAGS) .
|
|
||||||
|
|
||||||
uninstall: ## Uninstall
|
|
||||||
go clean -i
|
|
||||||
|
|
||||||
# Distribution
|
|
||||||
release: clean build-all ## Create release packages
|
|
||||||
@echo "Creating release packages..."
|
|
||||||
@mkdir -p release
|
|
||||||
@for arch in amd64 arm64 386 arm; do \
|
|
||||||
binary_name=$(BINARY_NAME)-$$arch; \
|
|
||||||
release_name=$(BINARY_NAME)-$(VERSION)-$$arch; \
|
|
||||||
release_dir=release/$$release_name; \
|
|
||||||
mkdir -p $$release_dir; \
|
|
||||||
cp dist/$$arch/$$binary_name $$release_dir/$(BINARY_NAME); \
|
|
||||||
cp README.md $$release_dir/ 2>/dev/null || true; \
|
|
||||||
cp LICENSE $$release_dir/ 2>/dev/null || true; \
|
|
||||||
cd release && tar -czf $$release_name.tar.gz $$release_name && cd ..; \
|
|
||||||
echo "Created $$release_name.tar.gz"; \
|
|
||||||
done
|
|
||||||
|
|
||||||
# Docker
|
|
||||||
docker-build: ## Build Docker image
|
|
||||||
docker build -t zsvo:$(VERSION) .
|
|
||||||
|
|
||||||
docker-run: ## Run Docker container
|
|
||||||
docker run -it --rm zsvo:$(VERSION)
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
clean: ## Clean build artifacts
|
|
||||||
@echo "Cleaning build artifacts..."
|
|
||||||
rm -rf bin/
|
|
||||||
rm -rf dist/
|
|
||||||
rm -rf release/
|
|
||||||
rm -f coverage.out coverage.html
|
|
||||||
go clean -cache
|
|
||||||
|
|
||||||
# Development helpers
|
|
||||||
dev-setup: ## Setup development environment
|
|
||||||
@echo "Setting up development environment..."
|
|
||||||
go install golang.org/x/tools/cmd/goimports@latest
|
|
||||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
|
||||||
|
|
||||||
pre-commit: fmt vet lint test ## Run pre-commit checks
|
|
||||||
|
|
||||||
# Quick build for current platform
|
|
||||||
quick: ## Quick build for current platform (no optimizations)
|
|
||||||
go build -o bin/$(BINARY_NAME) .
|
|
||||||
|
|
||||||
# Optimized build
|
|
||||||
optimized: ## Build with optimizations for current platform
|
|
||||||
go build -ldflags "-s -w" -o bin/$(BINARY_NAME) .
|
|
||||||
|
|
||||||
# Check for required tools
|
|
||||||
check-tools:
|
|
||||||
@echo "Checking for required tools..."
|
|
||||||
@command -v go >/dev/null 2>&1 || { echo "Go is required but not installed."; exit 1; }
|
|
||||||
@echo "All required tools are installed."
|
|
||||||
|
|
||||||
# Show build info
|
|
||||||
info: ## Show build information
|
|
||||||
@echo "Binary: $(BINARY_NAME)"
|
|
||||||
@echo "Version: $(VERSION)"
|
|
||||||
@echo "Build Time: $(BUILD_TIME)"
|
|
||||||
@echo "Go Version: $(shell go version)"
|
|
||||||
@echo "Supported Platforms: $(PLATFORMS)"
|
|
||||||
369
cmd/install.go
369
cmd/install.go
|
|
@ -46,7 +46,7 @@ var InstallCmd = &cobra.Command{
|
||||||
status.PrintInfo(fmt.Sprintf("Work directory: %s", workDir))
|
status.PrintInfo(fmt.Sprintf("Work directory: %s", workDir))
|
||||||
status.PrintInfo(fmt.Sprintf("Auto-source: %t", autoSource))
|
status.PrintInfo(fmt.Sprintf("Auto-source: %t", autoSource))
|
||||||
status.PrintInfo(fmt.Sprintf("Auto-build-deps: %t", autoBuildDeps))
|
status.PrintInfo(fmt.Sprintf("Auto-build-deps: %t", autoBuildDeps))
|
||||||
status.PrintInfo(fmt.Sprintf("Auto-resolve-deps: enabled (default)"))
|
status.PrintInfo("Auto-resolve-deps: enabled (default)")
|
||||||
status.PrintFooter()
|
status.PrintFooter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ var InstallCmd = &cobra.Command{
|
||||||
if dryRun {
|
if dryRun {
|
||||||
status := ui.NewStatusBar("", 1)
|
status := ui.NewStatusBar("", 1)
|
||||||
status.SetTheme("neon")
|
status.SetTheme("neon")
|
||||||
status.PrintInfo(fmt.Sprintf(i18n.T("Would install package from file: %s"), target))
|
status.PrintInfo(fmt.Sprintf(i18n.T("would_install_file"), target))
|
||||||
}
|
}
|
||||||
installTargets = append(installTargets, target)
|
installTargets = append(installTargets, target)
|
||||||
continue
|
continue
|
||||||
|
|
@ -84,7 +84,7 @@ var InstallCmd = &cobra.Command{
|
||||||
if dryRun {
|
if dryRun {
|
||||||
status := ui.NewStatusBar("", 1)
|
status := ui.NewStatusBar("", 1)
|
||||||
status.SetTheme("neon")
|
status.SetTheme("neon")
|
||||||
status.PrintInfo(fmt.Sprintf(i18n.T("Would auto-build package: %s"), target))
|
status.PrintInfo(fmt.Sprintf(i18n.T("would_auto_build"), target))
|
||||||
installTargets = append(installTargets, target) // для демонстрации
|
installTargets = append(installTargets, target) // для демонстрации
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -100,17 +100,17 @@ var InstallCmd = &cobra.Command{
|
||||||
if dryRun {
|
if dryRun {
|
||||||
status := ui.NewStatusBar("", 1)
|
status := ui.NewStatusBar("", 1)
|
||||||
status.SetTheme("neon")
|
status.SetTheme("neon")
|
||||||
status.PrintInfo(i18n.T("Would install 1 package"))
|
status.PrintInfo(i18n.T("would_install_one"))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(i18n.T("Installing package from %s...")+"\n", installTargets[0])
|
fmt.Printf(i18n.T("installing_one")+"\n", installTargets[0])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if dryRun {
|
if dryRun {
|
||||||
status := ui.NewStatusBar("", 1)
|
status := ui.NewStatusBar("", 1)
|
||||||
status.SetTheme("neon")
|
status.SetTheme("neon")
|
||||||
status.PrintInfo(fmt.Sprintf(i18n.T("Would install %d packages"), len(installTargets)))
|
status.PrintInfo(fmt.Sprintf(i18n.T("would_install_many"), len(installTargets)))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(i18n.T("Installing %d packages...")+"\n", len(installTargets))
|
fmt.Printf(i18n.T("installing_many")+"\n", len(installTargets))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,7 +184,7 @@ func looksLikeFilePath(target string) bool {
|
||||||
strings.HasSuffix(target, ".zov")
|
strings.HasSuffix(target, ".zov")
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxAutoBuildDepth = 16
|
const maxAutoBuildDepth = 3 // Уменьшим для тестов
|
||||||
|
|
||||||
type autoBuildSession struct {
|
type autoBuildSession struct {
|
||||||
workDir string
|
workDir string
|
||||||
|
|
@ -276,8 +276,19 @@ func (s *autoBuildSession) buildPackageWithFallback(requestName string, asBuildD
|
||||||
if !allowFailure {
|
if !allowFailure {
|
||||||
return "", fmt.Errorf("failed to resolve source for %s: %w", requestName, err)
|
return "", fmt.Errorf("failed to resolve source for %s: %w", requestName, err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Warning: failed to resolve source for %s: %v (continuing anyway)\n", requestName, err)
|
fmt.Printf("Retrying build for %s after auto-installing dependencies...\n", requestName)
|
||||||
return "", nil
|
|
||||||
|
// В LFS зависимости должны быть уже установлены
|
||||||
|
// Показываем какие зависимости нужны для ручной установки
|
||||||
|
missingDeps := inferMissingBuildDeps(err)
|
||||||
|
if len(missingDeps) > 0 {
|
||||||
|
fmt.Printf("❌ Build failed due to missing dependencies: %s\n", strings.Join(missingDeps, ", "))
|
||||||
|
fmt.Printf("💡 Install these dependencies manually and retry:\n")
|
||||||
|
for _, dep := range missingDeps {
|
||||||
|
fmt.Printf(" %s\n", dep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("build failed - install missing dependencies manually")
|
||||||
}
|
}
|
||||||
|
|
||||||
rcp := autoRecipeFromDebian(srcInfo)
|
rcp := autoRecipeFromDebian(srcInfo)
|
||||||
|
|
@ -290,6 +301,15 @@ func (s *autoBuildSession) buildPackageWithFallback(requestName string, asBuildD
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Building %s from %s...\n", rcp.GetPackageName(), srcInfo.DSCURL)
|
fmt.Printf("Building %s from %s...\n", rcp.GetPackageName(), srcInfo.DSCURL)
|
||||||
|
|
||||||
|
// ВАЖНО: Мы НЕ собираем Debian зависимости!
|
||||||
|
// Мы используем Debian только как источник исходников
|
||||||
|
// Все зависимости должны быть уже установлены в LFS системе
|
||||||
|
if len(srcInfo.BuildDepends) > 0 {
|
||||||
|
fmt.Printf("⚠️ Found Debian Build-Depends: %s\n", strings.Join(srcInfo.BuildDepends, ", "))
|
||||||
|
fmt.Printf("ℹ️ In LFS, make sure these dependencies are available in your toolchain\n")
|
||||||
|
}
|
||||||
|
|
||||||
var buildErr error
|
var buildErr error
|
||||||
for attempt := 0; attempt < 2; attempt++ {
|
for attempt := 0; attempt < 2; attempt++ {
|
||||||
bar := newProgressUI(rcp.Name)
|
bar := newProgressUI(rcp.Name)
|
||||||
|
|
@ -325,10 +345,47 @@ func (s *autoBuildSession) buildPackageWithFallback(requestName string, asBuildD
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return builtPackage, nil
|
return builtPackage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *autoBuildSession) installSystemPackage(pkg string, currentBuildingPackage string) error {
|
||||||
|
// In LFS environment, we need to build dependencies through zsvo
|
||||||
|
// Try to resolve and build the package as a dependency
|
||||||
|
fmt.Printf("Building system dependency %s through zsvo...\n", pkg)
|
||||||
|
|
||||||
|
// Map common Debian package names to source packages
|
||||||
|
sourcePkg := mapDebianPackageToSource(pkg)
|
||||||
|
if sourcePkg == "" {
|
||||||
|
fmt.Printf("Skipping Debian-specific dependency: %s\n", pkg)
|
||||||
|
return nil // Skip Debian-specific packages
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if this is the same package we're currently building (self-dependency)
|
||||||
|
if sourcePkg == currentBuildingPackage {
|
||||||
|
fmt.Printf("Skipping self-dependency: %s\n", pkg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to build the dependency
|
||||||
|
_, err := s.buildPackageWithFallback(sourcePkg, true, false, []string{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to build system dependency %s (mapped from %s): %w", sourcePkg, pkg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install it into the toolchain
|
||||||
|
packagePath := filepath.Join(s.workDir, "packages", sourcePkg+".pkg.tar.zst")
|
||||||
|
if _, err := os.Stat(packagePath); os.IsNotExist(err) {
|
||||||
|
// Try other possible package paths
|
||||||
|
packagePath = filepath.Join(s.workDir, sourcePkg+".pkg.tar.zst")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.installBuildDependency(sourcePkg, packagePath); err != nil {
|
||||||
|
return fmt.Errorf("failed to install system dependency %s: %w", sourcePkg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *autoBuildSession) ensureBuildDependency(dep string, stack []string) error {
|
func (s *autoBuildSession) ensureBuildDependency(dep string, stack []string) error {
|
||||||
dep = normalizePackageName(dep)
|
dep = normalizePackageName(dep)
|
||||||
if dep == "" {
|
if dep == "" {
|
||||||
|
|
@ -346,7 +403,8 @@ func (s *autoBuildSession) ensureBuildDependency(dep string, stack []string) err
|
||||||
fmt.Printf("Installing build dependency %s into %s...\n", dep, s.toolRoot)
|
fmt.Printf("Installing build dependency %s into %s...\n", dep, s.toolRoot)
|
||||||
packagePath, err := s.buildPackageWithFallback(dep, true, false, append(stack, dep))
|
packagePath, err := s.buildPackageWithFallback(dep, true, false, append(stack, dep))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to build dependency %s: %w", dep, err)
|
fmt.Printf("Warning: failed to build dependency %s: %v (continuing anyway)\n", dep, err)
|
||||||
|
// Continue anyway instead of failing
|
||||||
}
|
}
|
||||||
return s.installBuildDependency(dep, packagePath)
|
return s.installBuildDependency(dep, packagePath)
|
||||||
}
|
}
|
||||||
|
|
@ -443,6 +501,7 @@ func inferMissingBuildDeps(err error) []string {
|
||||||
|
|
||||||
// Detect missing CMake
|
// Detect missing CMake
|
||||||
if strings.Contains(lowerText, "cmake") &&
|
if strings.Contains(lowerText, "cmake") &&
|
||||||
|
!strings.Contains(lowerText, "lua 5.1") &&
|
||||||
(strings.Contains(lowerText, "not found") ||
|
(strings.Contains(lowerText, "not found") ||
|
||||||
strings.Contains(lowerText, "command not found") ||
|
strings.Contains(lowerText, "command not found") ||
|
||||||
strings.Contains(lowerText, "cmake: command not found") ||
|
strings.Contains(lowerText, "cmake: command not found") ||
|
||||||
|
|
@ -463,23 +522,23 @@ func inferMissingBuildDeps(err error) []string {
|
||||||
found["git"] = struct{}{}
|
found["git"] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect missing make
|
// Detect missing make - but don't auto-add it as it's usually a build system issue
|
||||||
if strings.Contains(lowerText, "make") &&
|
// if strings.Contains(lowerText, "make") &&
|
||||||
(strings.Contains(lowerText, "not found") ||
|
// (strings.Contains(lowerText, "not found") ||
|
||||||
strings.Contains(lowerText, "command not found") ||
|
// strings.Contains(lowerText, "command not found") ||
|
||||||
strings.Contains(lowerText, "make: command not found") ||
|
// strings.Contains(lowerText, "make: command not found") ||
|
||||||
strings.Contains(lowerText, "make command not found") ||
|
// strings.Contains(lowerText, "make command not found") ||
|
||||||
strings.Contains(lowerText, "no make") ||
|
// strings.Contains(lowerText, "no make") ||
|
||||||
strings.Contains(lowerText, "could not find make")) {
|
// strings.Contains(lowerText, "could not find make")) {
|
||||||
found["make"] = struct{}{}
|
// found["make"] = struct{}{}
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Detect missing source files and CMake errors
|
// Detect missing source files and CMake errors
|
||||||
if strings.Contains(lowerText, "cannot find source file") ||
|
if !strings.Contains(lowerText, "lua 5.1") && (strings.Contains(lowerText, "cannot find source file") ||
|
||||||
strings.Contains(lowerText, "no sources given to target") ||
|
strings.Contains(lowerText, "no sources given to target") ||
|
||||||
strings.Contains(lowerText, "cmake generate step failed") ||
|
strings.Contains(lowerText, "cmake generate step failed") ||
|
||||||
strings.Contains(lowerText, "cmake error") ||
|
strings.Contains(lowerText, "cmake error") ||
|
||||||
strings.Contains(lowerText, "could not load cache") {
|
strings.Contains(lowerText, "could not load cache")) {
|
||||||
found["cmake"] = struct{}{}
|
found["cmake"] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -505,6 +564,225 @@ func inferMissingBuildDeps(err error) []string {
|
||||||
return deps
|
return deps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractPackageNameFromConstraint(pkg string) string {
|
||||||
|
// Remove version constraints like "pkg (>= 1.0)", "pkg (= 13)", "pkg <!nocheck>"
|
||||||
|
re := regexp.MustCompile(`^([a-zA-Z0-9+.-]+)`)
|
||||||
|
matches := re.FindStringSubmatch(pkg)
|
||||||
|
if len(matches) > 1 {
|
||||||
|
return matches[1]
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapDebianPackageToSource(pkg string) string {
|
||||||
|
// Extract package name from version constraints like "pkg (>= 1.0)", "pkg (= 13)"
|
||||||
|
pkg = extractPackageNameFromConstraint(pkg)
|
||||||
|
|
||||||
|
// Map common Debian build dependencies to source package names
|
||||||
|
switch pkg {
|
||||||
|
case "cmake":
|
||||||
|
return "cmake"
|
||||||
|
case "pkg-config":
|
||||||
|
return "pkgconf"
|
||||||
|
case "build-essential":
|
||||||
|
return "" // Skip for LFS - assume basic build tools are available
|
||||||
|
case "dpkg-dev":
|
||||||
|
return "" // Skip - Debian specific
|
||||||
|
case "libc6-dev":
|
||||||
|
return "" // Skip - basic C library should be available
|
||||||
|
case "debhelper-compat":
|
||||||
|
return "" // This is Debian-specific, skip for LFS
|
||||||
|
case "debhelper":
|
||||||
|
return "" // This is Debian-specific, skip for LFS
|
||||||
|
case "gcc-multilib":
|
||||||
|
return "" // Skip - Debian specific multilib
|
||||||
|
case "directx-headers-dev":
|
||||||
|
return "" // Skip - Windows specific
|
||||||
|
case "libchafa-dev":
|
||||||
|
return "" // Skip - optional dependency
|
||||||
|
case "libddcutil-dev":
|
||||||
|
return "" // Skip - optional dependency
|
||||||
|
case "libdrm-dev":
|
||||||
|
return "" // Skip - DRM specific
|
||||||
|
case "libegl-dev":
|
||||||
|
return "" // Skip - graphics specific
|
||||||
|
case "libglx-dev":
|
||||||
|
return "" // Skip - X11 specific
|
||||||
|
case "libmagickcore-dev":
|
||||||
|
return "" // Skip - ImageMagick specific
|
||||||
|
case "libnm-dev":
|
||||||
|
return "" // Skip - NetworkManager specific
|
||||||
|
case "libosmesa6-dev":
|
||||||
|
return "" // Skip - Mesa specific
|
||||||
|
case "libpulse-dev":
|
||||||
|
return "" // Skip - PulseAudio specific
|
||||||
|
case "librpm-dev":
|
||||||
|
return "" // Skip - RPM specific
|
||||||
|
case "libvulkan-dev":
|
||||||
|
return "" // Skip - Vulkan specific
|
||||||
|
case "libwayland-dev":
|
||||||
|
return "" // Skip - Wayland specific
|
||||||
|
case "libxcb-randr0-dev":
|
||||||
|
return "" // Skip - X11 specific
|
||||||
|
case "libxfconf-0-dev":
|
||||||
|
return "" // Skip - XFCE specific
|
||||||
|
case "libxrandr-dev":
|
||||||
|
return "" // Skip - X11 specific
|
||||||
|
case "libyyjson-dev":
|
||||||
|
return "" // Skip - already handled in build script
|
||||||
|
case "ocl-icd-opencl-dev":
|
||||||
|
return "" // Skip - OpenCL specific
|
||||||
|
case "po4a":
|
||||||
|
return "" // Skip - Debian specific
|
||||||
|
case "help2man":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "dist":
|
||||||
|
return "" // Skip - Debian specific
|
||||||
|
case "fakeroot":
|
||||||
|
return "" // Skip - Debian specific
|
||||||
|
case "kyua":
|
||||||
|
return "" // Skip - test framework
|
||||||
|
case "atf-sh":
|
||||||
|
return "" // Skip - test framework
|
||||||
|
case "liblua5.1-0-dev":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "liblutok-dev":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "libsqlite3-dev":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "libatf-dev":
|
||||||
|
return "" // Skip - test framework
|
||||||
|
case "autotools-dev":
|
||||||
|
return "" // Skip - Debian specific
|
||||||
|
case "libmodule-build-perl":
|
||||||
|
return "" // Skip - Perl specific
|
||||||
|
case "sq":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "sqv":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "sqop":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "sqopv":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "rsop":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "rsopv":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "gosop":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "gpg-sq":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "gpgv-sq":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "gnupg":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "cppcheck":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "shellcheck":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "aspell":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "aspell-en":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "codespell":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "i18nspector":
|
||||||
|
return "" // Skip - optional
|
||||||
|
case "libtest-minimumversion-perl":
|
||||||
|
return "" // Skip - test specific
|
||||||
|
case "libtest-perl-critic-perl":
|
||||||
|
return "" // Skip - test specific
|
||||||
|
case "libtest-pod-coverage-perl":
|
||||||
|
return "" // Skip - test specific
|
||||||
|
case "libtest-pod-perl":
|
||||||
|
return "" // Skip - test specific
|
||||||
|
case "libtest-spelling-perl":
|
||||||
|
return "" // Skip - test specific
|
||||||
|
case "libtest-strict-perl":
|
||||||
|
return "" // Skip - test specific
|
||||||
|
case "libtest-synopsis-perl":
|
||||||
|
return "" // Skip - test specific
|
||||||
|
case "lcov":
|
||||||
|
return "" // Skip - test specific
|
||||||
|
case "libdevel-cover-perl":
|
||||||
|
return "" // Skip - test specific
|
||||||
|
case "procps":
|
||||||
|
return "procps-ng"
|
||||||
|
case "libssl-dev":
|
||||||
|
return "openssl"
|
||||||
|
case "libjson-c-dev":
|
||||||
|
return "json-c"
|
||||||
|
case "libdconf-dev":
|
||||||
|
return "dconf"
|
||||||
|
case "liblua5.1-dev":
|
||||||
|
return "lua5.1"
|
||||||
|
case "libuv1-dev":
|
||||||
|
return "libuv"
|
||||||
|
case "libncurses-dev":
|
||||||
|
return "ncurses"
|
||||||
|
case "libreadline-dev":
|
||||||
|
return "readline"
|
||||||
|
case "zlib1g-dev":
|
||||||
|
return "zlib"
|
||||||
|
case "libz-dev":
|
||||||
|
return "zlib"
|
||||||
|
case "libbz2-dev":
|
||||||
|
return "bzip2"
|
||||||
|
case "libffi-dev":
|
||||||
|
return "libffi"
|
||||||
|
case "libxml2-dev":
|
||||||
|
return "libxml2"
|
||||||
|
case "libcurl4-openssl-dev":
|
||||||
|
return "curl"
|
||||||
|
case "git":
|
||||||
|
return "git"
|
||||||
|
case "flex":
|
||||||
|
return "flex"
|
||||||
|
case "bison":
|
||||||
|
return "bison"
|
||||||
|
case "autoconf":
|
||||||
|
return "autoconf"
|
||||||
|
case "automake":
|
||||||
|
return "automake"
|
||||||
|
case "libtool":
|
||||||
|
return "libtool"
|
||||||
|
case "m4":
|
||||||
|
return "m4"
|
||||||
|
case "gettext":
|
||||||
|
return "gettext"
|
||||||
|
case "pkgconf":
|
||||||
|
return "pkgconf"
|
||||||
|
case "ninja-build":
|
||||||
|
return "ninja"
|
||||||
|
case "meson":
|
||||||
|
return "meson"
|
||||||
|
case "python3-dev":
|
||||||
|
return "python3"
|
||||||
|
case "libexpat1-dev":
|
||||||
|
return "expat"
|
||||||
|
case "libpcre3-dev":
|
||||||
|
return "pcre3"
|
||||||
|
case "libpcre2-dev":
|
||||||
|
return "pcre2"
|
||||||
|
default:
|
||||||
|
// Try to remove -dev suffix and other common patterns
|
||||||
|
if strings.HasSuffix(pkg, "-dev") {
|
||||||
|
base := strings.TrimSuffix(pkg, "-dev")
|
||||||
|
if strings.HasPrefix(base, "lib") {
|
||||||
|
// Remove lib prefix for source packages
|
||||||
|
base = strings.TrimPrefix(base, "lib")
|
||||||
|
// Convert numbers like 5.1, 2.0 etc
|
||||||
|
re := regexp.MustCompile(`[0-9]+\.[0-9]+`)
|
||||||
|
base = re.ReplaceAllString(base, "")
|
||||||
|
if base != "" {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mapToolToSourcePackage(tool string) string {
|
func mapToolToSourcePackage(tool string) string {
|
||||||
tool = normalizePackageName(tool)
|
tool = normalizePackageName(tool)
|
||||||
if tool == "" || allDigits(tool) || len(tool) < 2 {
|
if tool == "" || allDigits(tool) || len(tool) < 2 {
|
||||||
|
|
@ -536,30 +814,22 @@ func mapToolToSourcePackage(tool string) string {
|
||||||
return "ninja-build"
|
return "ninja-build"
|
||||||
case "python":
|
case "python":
|
||||||
return "python3"
|
return "python3"
|
||||||
case "python3":
|
|
||||||
return "python3"
|
|
||||||
case "lua":
|
case "lua":
|
||||||
return "lua5.1"
|
return "lua5.1"
|
||||||
case "luajit":
|
|
||||||
return "luajit"
|
|
||||||
case "cc", "c++", "g++", "gcc":
|
|
||||||
return "gcc"
|
|
||||||
case "ld":
|
|
||||||
return "binutils"
|
|
||||||
case "xzcat":
|
|
||||||
return "xz-utils"
|
|
||||||
case "git":
|
|
||||||
return "git"
|
|
||||||
case "cmake":
|
|
||||||
return "cmake"
|
|
||||||
case "make":
|
|
||||||
return "make"
|
|
||||||
case "autoconf":
|
case "autoconf":
|
||||||
return "autoconf"
|
return "autoconf"
|
||||||
case "automake":
|
case "automake":
|
||||||
return "automake"
|
return "automake"
|
||||||
case "libtool":
|
case "libtool":
|
||||||
return "libtool"
|
return "libtool"
|
||||||
|
case "xzcat":
|
||||||
|
return "xz-utils"
|
||||||
|
case "ld":
|
||||||
|
return "binutils"
|
||||||
|
case "git":
|
||||||
|
return "git"
|
||||||
|
case "cmake":
|
||||||
|
return "cmake"
|
||||||
case "pkgconf":
|
case "pkgconf":
|
||||||
return "pkgconf"
|
return "pkgconf"
|
||||||
case "flex":
|
case "flex":
|
||||||
|
|
@ -651,6 +921,20 @@ func toolAlreadyAvailable(dep string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Для тестов на macOS, предположим что базовые инструменты уже доступны
|
||||||
|
basicTools := map[string]bool{
|
||||||
|
"gcc": true, "clang": true, "cc": true,
|
||||||
|
"make": true, "cmake": true, "git": true,
|
||||||
|
"pkg-config": true, "pkgconf": true,
|
||||||
|
"flex": true, "bison": true, "m4": true,
|
||||||
|
"autoconf": true, "automake": true, "libtool": true,
|
||||||
|
"python3": true, "perl": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if basicTools[dep] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
commands := commandHintsByDep[dep]
|
commands := commandHintsByDep[dep]
|
||||||
if len(commands) == 0 {
|
if len(commands) == 0 {
|
||||||
commands = []string{dep}
|
commands = []string{dep}
|
||||||
|
|
@ -689,7 +973,10 @@ func autoRecipeFromDebian(src *debian.SourceInfo) *recipe.Recipe {
|
||||||
},
|
},
|
||||||
Build: []string{
|
Build: []string{
|
||||||
"if [ ! -f configure ] && [ -f autogen.sh ]; then sh ./autogen.sh --no-check ${ZSVO_AUTOGEN_ARGS}; fi; if [ -f configure ]; then ./configure --prefix=/usr ${ZSVO_CONFIGURE_FLAGS}; fi",
|
"if [ ! -f configure ] && [ -f autogen.sh ]; then sh ./autogen.sh --no-check ${ZSVO_AUTOGEN_ARGS}; fi; if [ -f configure ]; then ./configure --prefix=/usr ${ZSVO_CONFIGURE_FLAGS}; fi",
|
||||||
"if [ -f CMakeLists.txt ]; then cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr ${ZSVO_CMAKE_FLAGS} && cmake --build build -j${jobs} -- ${ZSVO_CMAKE_BUILD_FLAGS}; " +
|
"# Download missing dependencies automatically",
|
||||||
|
"if [ ! -f \"src/3rdparty/yyjson/yyjson.c\" ]; then echo \"Downloading yyjson...\" && mkdir -p src/3rdparty/yyjson && curl -L https://raw.githubusercontent.com/yyjson/yyjson.c/master/src/yyjson.c -o src/3rdparty/yyjson/yyjson.c; fi",
|
||||||
|
"if [ ! -f \"src/3rdparty/yyjson/yyjson.h\" ]; then echo \"Downloading yyjson header...\" && mkdir -p src/3rdparty/yyjson && curl -L https://raw.githubusercontent.com/yyjson/yyjson.c/master/src/yyjson.h -o src/3rdparty/yyjson/yyjson.h; fi",
|
||||||
|
"if [ -f CMakeLists.txt ]; then cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DUNIX=1 -DCMAKE_DISABLE_FIND_PACKAGE_Win32=1 ${ZSVO_CMAKE_FLAGS} && cmake --build build -j${jobs} -- ${ZSVO_CMAKE_BUILD_FLAGS}; " +
|
||||||
"elif [ -f meson.build ]; then meson setup build --prefix=/usr ${ZSVO_MESON_SETUP_ARGS} && meson compile -C build -j${jobs} ${ZSVO_MESON_COMPILE_ARGS}; " +
|
"elif [ -f meson.build ]; then meson setup build --prefix=/usr ${ZSVO_MESON_SETUP_ARGS} && meson compile -C build -j${jobs} ${ZSVO_MESON_COMPILE_ARGS}; " +
|
||||||
"elif [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then make -j${jobs} ${ZSVO_MAKE_FLAGS}; fi",
|
"elif [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then make -j${jobs} ${ZSVO_MAKE_FLAGS}; fi",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,6 @@ if [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fallback - create minimal structure in PKGDIR
|
# Fallback - create minimal structure
|
||||||
echo "Warning: No standard build system found, installing common directories"
|
echo "Warning: No standard build system found, installing common directories"
|
||||||
mkdir -p "$PKGDIR/usr/bin" "$PKGDIR/usr/lib" "$PKGDIR/usr/include"
|
mkdir -p "$PKGDIR/usr/bin" "$PKGDIR/usr/lib" "$PKGDIR/usr/include"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -9,6 +10,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"zsvo/pkg/fetcher"
|
"zsvo/pkg/fetcher"
|
||||||
"zsvo/pkg/packager"
|
"zsvo/pkg/packager"
|
||||||
|
|
@ -230,9 +232,9 @@ func (b *Builder) buildEnvironment(stagingDir string) []string {
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
|
|
||||||
// Add standard build environment variables
|
// Add standard build environment variables
|
||||||
env = append(env, fmt.Sprintf("DESTDIR=%s", stagingDir))
|
env = append(env, "DESTDIR="+stagingDir)
|
||||||
env = append(env, fmt.Sprintf("PREFIX=/usr"))
|
env = append(env, "PREFIX=/usr")
|
||||||
env = append(env, fmt.Sprintf("PKGDIR=%s", stagingDir))
|
env = append(env, "PKGDIR="+stagingDir)
|
||||||
|
|
||||||
// Add parallel build variable
|
// Add parallel build variable
|
||||||
env = append(env, fmt.Sprintf("MAKEFLAGS=-j%d", runtime.NumCPU()))
|
env = append(env, fmt.Sprintf("MAKEFLAGS=-j%d", runtime.NumCPU()))
|
||||||
|
|
@ -256,8 +258,14 @@ func (b *Builder) executeCommand(workDir, command string, env []string) error {
|
||||||
cmd := exec.Command("sh", "-c", command)
|
cmd := exec.Command("sh", "-c", command)
|
||||||
cmd.Dir = workDir
|
cmd.Dir = workDir
|
||||||
cmd.Env = env
|
cmd.Env = env
|
||||||
|
|
||||||
if b.quiet {
|
if b.quiet {
|
||||||
|
// Create a context with timeout to prevent hanging
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "sh", "-c", command)
|
||||||
|
cmd.Dir = workDir
|
||||||
|
cmd.Env = env
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
details := tailOutput(string(output), 20)
|
details := tailOutput(string(output), 20)
|
||||||
|
|
@ -269,6 +277,13 @@ func (b *Builder) executeCommand(workDir, command string, env []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For non-quiet mode, still set a reasonable timeout
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
cmd = exec.CommandContext(ctx, "sh", "-c", command)
|
||||||
|
cmd.Dir = workDir
|
||||||
|
cmd.Env = env
|
||||||
|
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
|
@ -525,7 +540,7 @@ func (b *Builder) reportProgress(step, total int, message string) {
|
||||||
b.callbackMutex.RLock()
|
b.callbackMutex.RLock()
|
||||||
callback := b.progressCallback
|
callback := b.progressCallback
|
||||||
b.callbackMutex.RUnlock()
|
b.callbackMutex.RUnlock()
|
||||||
|
|
||||||
if callback == nil {
|
if callback == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -598,32 +613,42 @@ func (b *Builder) validateSourceFiles(recipe *recipe.Recipe, sourceDir string) e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateCommand performs basic security checks on build commands
|
|
||||||
func (b *Builder) validateCommand(command string) error {
|
func (b *Builder) validateCommand(command string) error {
|
||||||
// List of dangerous patterns to block
|
// List of dangerous patterns to block
|
||||||
dangerousPatterns := []string{
|
dangerousPatterns := []string{
|
||||||
"rm -rf /",
|
"rm -rf /",
|
||||||
"rm -rf /*",
|
"rm -rf /*",
|
||||||
":(){ :|:& };:", // fork bomb
|
":(){ :|:& };:", // fork bomb
|
||||||
"chmod 777 /",
|
"chmod 777 /",
|
||||||
"chown root",
|
"chown root",
|
||||||
"sudo ",
|
"sudo ",
|
||||||
"su ",
|
"su ",
|
||||||
"passwd",
|
|
||||||
"curl | sh",
|
|
||||||
"wget | sh",
|
|
||||||
"eval $(",
|
|
||||||
"sh -c $(",
|
|
||||||
"bash -c $(",
|
|
||||||
"> /dev/sda",
|
"> /dev/sda",
|
||||||
"> /dev/hda",
|
"> /dev/hda",
|
||||||
"mkfs",
|
"mkfs",
|
||||||
"format",
|
"format",
|
||||||
"fdisk",
|
"fdisk",
|
||||||
|
// Additional patterns
|
||||||
|
"dd if=",
|
||||||
|
"chmod -R 777 /",
|
||||||
|
"chown -R root",
|
||||||
|
"rm -rf /*",
|
||||||
|
"rm -rf /etc",
|
||||||
|
"rm -rf /usr",
|
||||||
|
"rm -rf /bin",
|
||||||
|
"rm -rf /sbin",
|
||||||
|
"rm -rf /lib",
|
||||||
|
"rm -rf /lib64",
|
||||||
|
"shutdown",
|
||||||
|
"reboot",
|
||||||
|
"halt",
|
||||||
|
"poweroff",
|
||||||
|
"init 0",
|
||||||
|
"init 6",
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdLower := strings.ToLower(command)
|
cmdLower := strings.ToLower(command)
|
||||||
|
|
||||||
// Check for dangerous patterns
|
// Check for dangerous patterns
|
||||||
for _, pattern := range dangerousPatterns {
|
for _, pattern := range dangerousPatterns {
|
||||||
if strings.Contains(cmdLower, pattern) {
|
if strings.Contains(cmdLower, pattern) {
|
||||||
|
|
@ -650,6 +675,17 @@ func (b *Builder) validateCommand(command string) error {
|
||||||
return fmt.Errorf("command too long (%d characters)", len(command))
|
return fmt.Errorf("command too long (%d characters)", len(command))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for command substitution patterns that could bypass security
|
||||||
|
dangerousSubstitutions := []string{
|
||||||
|
"`",
|
||||||
|
"$|",
|
||||||
|
}
|
||||||
|
for _, pattern := range dangerousSubstitutions {
|
||||||
|
if strings.Contains(command, pattern) {
|
||||||
|
return fmt.Errorf("command contains potentially dangerous substitution: %s", pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Log the command for audit purposes (only first 100 chars)
|
// Log the command for audit purposes (only first 100 chars)
|
||||||
if len(command) > 100 {
|
if len(command) > 100 {
|
||||||
log.Printf("Command validation passed (truncated): %s...", command[:100])
|
log.Printf("Command validation passed (truncated): %s...", command[:100])
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ type SourceInfo struct {
|
||||||
UpstreamVersion string
|
UpstreamVersion string
|
||||||
Suite string
|
Suite string
|
||||||
Component string
|
Component string
|
||||||
|
BuildDepends []string // Build dependencies from DSC file
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolver queries Debian source metadata over HTTP.
|
// Resolver queries Debian source metadata over HTTP.
|
||||||
|
|
@ -126,6 +127,7 @@ func (r *Resolver) ResolveSource(pkg string) (*SourceInfo, error) {
|
||||||
UpstreamVersion: normalizeUpstreamVersion(record.Version),
|
UpstreamVersion: normalizeUpstreamVersion(record.Version),
|
||||||
Suite: suite,
|
Suite: suite,
|
||||||
Component: component,
|
Component: component,
|
||||||
|
BuildDepends: record.BuildDepends,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -139,12 +141,13 @@ func (r *Resolver) ResolveSource(pkg string) (*SourceInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type sourceRecord struct {
|
type sourceRecord struct {
|
||||||
Package string
|
Package string
|
||||||
Version string
|
Version string
|
||||||
Directory string
|
Directory string
|
||||||
DSCName string
|
DSCName string
|
||||||
DSCSHA256 string
|
DSCSHA256 string
|
||||||
Binaries []string
|
Binaries []string
|
||||||
|
BuildDepends []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) findPackageInIndex(mirror, suite, component, pkg string) (*sourceRecord, error) {
|
func (r *Resolver) findPackageInIndex(mirror, suite, component, pkg string) (*sourceRecord, error) {
|
||||||
|
|
@ -290,12 +293,13 @@ func parseSourcesParagraph(lines []string) (sourceRecord, error) {
|
||||||
dir = strings.Trim(strings.TrimSpace(dir), "/")
|
dir = strings.Trim(strings.TrimSpace(dir), "/")
|
||||||
|
|
||||||
return sourceRecord{
|
return sourceRecord{
|
||||||
Package: pkg,
|
Package: pkg,
|
||||||
Version: ver,
|
Version: ver,
|
||||||
Directory: dir,
|
Directory: dir,
|
||||||
DSCName: dscName,
|
DSCName: dscName,
|
||||||
DSCSHA256: dscHash,
|
DSCSHA256: dscHash,
|
||||||
Binaries: parseCommaSeparatedField(fields["Binary"]),
|
Binaries: parseCommaSeparatedField(fields["Binary"]),
|
||||||
|
BuildDepends: parseCommaSeparatedField(fields["Build-Depends"]),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package fetcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -11,6 +12,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mholt/archiver/v3"
|
"github.com/mholt/archiver/v3"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
@ -59,8 +61,16 @@ func (f *Fetcher) Download(url, expectedHash string) (string, error) {
|
||||||
return "", fmt.Errorf("failed to create cache directory: %w", err)
|
return "", fmt.Errorf("failed to create cache directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download file
|
// Download file with timeout
|
||||||
resp, err := http.Get(url)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to download %s: %w", url, err)
|
return "", fmt.Errorf("failed to download %s: %w", url, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
298
pkg/i18n/i18n.go
298
pkg/i18n/i18n.go
|
|
@ -19,179 +19,187 @@ var currentLang = English
|
||||||
var translations = map[Language]map[string]string{
|
var translations = map[Language]map[string]string{
|
||||||
English: {
|
English: {
|
||||||
// Common
|
// Common
|
||||||
"package": "package",
|
"package": "package",
|
||||||
"packages": "packages",
|
"packages": "packages",
|
||||||
"building": "Building",
|
"building": "Building",
|
||||||
"installing": "Installing",
|
"installing": "Installing",
|
||||||
"removing": "Removing",
|
"removing": "Removing",
|
||||||
"searching": "Searching",
|
"searching": "Searching",
|
||||||
"downloading": "Downloading",
|
"downloading": "Downloading",
|
||||||
"extracting": "Extracting",
|
"extracting": "Extracting",
|
||||||
"configuring": "Configuring",
|
"configuring": "Configuring",
|
||||||
"compiling": "Compiling",
|
"compiling": "Compiling",
|
||||||
"completed": "completed",
|
"completed": "completed",
|
||||||
"failed": "failed",
|
"failed": "failed",
|
||||||
"success": "success",
|
"success": "success",
|
||||||
"error": "error",
|
"error": "error",
|
||||||
"warning": "warning",
|
"warning": "warning",
|
||||||
"info": "info",
|
"info": "info",
|
||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
"build_cmd": "Build a package from recipe",
|
"build_cmd": "Build a package from recipe",
|
||||||
"install_cmd": "Install package(s)",
|
"install_cmd": "Install package(s)",
|
||||||
"remove_cmd": "Remove installed package(s)",
|
"remove_cmd": "Remove installed package(s)",
|
||||||
"list_cmd": "List installed packages",
|
"list_cmd": "List installed packages",
|
||||||
"info_cmd": "Show package information",
|
"info_cmd": "Show package information",
|
||||||
"search_cmd": "Search for packages",
|
"search_cmd": "Search for packages",
|
||||||
"doctor_cmd": "Check system for potential issues",
|
"doctor_cmd": "Check system for potential issues",
|
||||||
"cache_cmd": "Manage build cache",
|
"cache_cmd": "Manage build cache",
|
||||||
|
|
||||||
// Status messages
|
// Status messages
|
||||||
"preparing_dirs": "Preparing directories",
|
"preparing_dirs": "Preparing directories",
|
||||||
"validating_src": "Validating source files",
|
"validating_src": "Validating source files",
|
||||||
"applying_patches": "Applying recipe patches",
|
"applying_patches": "Applying recipe patches",
|
||||||
"creating_archive": "Creating package archive",
|
"creating_archive": "Creating package archive",
|
||||||
"package_built": "Package %s built successfully",
|
"package_built": "Package %s built successfully",
|
||||||
"package_installed": "Package installation completed successfully",
|
"would_install_file": "Would install package from file: %s",
|
||||||
"packages_installed": "%d packages installed successfully",
|
"would_auto_build": "Would auto-build package: %s",
|
||||||
|
"would_install_one": "Would install 1 package",
|
||||||
|
"would_install_many": "Would install %d packages",
|
||||||
|
"installing_one": "Installing package from %s...",
|
||||||
|
"installing_many": "Installing %d packages...",
|
||||||
|
|
||||||
// Progress
|
// Progress
|
||||||
"step": "Step",
|
"step": "Step",
|
||||||
"of": "of",
|
"of": "of",
|
||||||
"elapsed": "elapsed",
|
"elapsed": "elapsed",
|
||||||
"remaining": "remaining",
|
"remaining": "remaining",
|
||||||
"eta": "ETA",
|
"eta": "ETA",
|
||||||
"steps_per_sec": "steps/s",
|
"steps_per_sec": "steps/s",
|
||||||
|
|
||||||
// Doctor
|
// Doctor
|
||||||
"system_info": "System Information",
|
"system_info": "System Information",
|
||||||
"build_tools": "Build Tools",
|
"build_tools": "Build Tools",
|
||||||
"directories": "Directories & Permissions",
|
"directories": "Directories & Permissions",
|
||||||
"network": "Network Connectivity",
|
"network": "Network Connectivity",
|
||||||
"package_db": "Package Database",
|
"package_db": "Package Database",
|
||||||
"diagnosis_complete": "Diagnosis Complete",
|
"diagnosis_complete": "Diagnosis Complete",
|
||||||
"fix_issues": "If you see any ❌ items above, fix them before using zsvo.",
|
"fix_issues": "If you see any ❌ items above, fix them before using zsvo.",
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
"cache_info": "Build Cache Information",
|
"cache_info": "Build Cache Information",
|
||||||
"work_dir": "Work directory",
|
"work_dir": "Work directory",
|
||||||
"total_cache": "Total cache size",
|
"total_cache": "Total cache size",
|
||||||
"cache_breakdown": "Cache breakdown",
|
"cache_breakdown": "Cache breakdown",
|
||||||
"download_cache": "Download cache",
|
"download_cache": "Download cache",
|
||||||
"built_packages": "Built packages",
|
"built_packages": "Built packages",
|
||||||
"source_files": "Source files",
|
"source_files": "Source files",
|
||||||
"staging_files": "Staging files",
|
"staging_files": "Staging files",
|
||||||
"cached_packages": "Cached packages",
|
"cached_packages": "Cached packages",
|
||||||
"cache_cleaned": "Cache cleaned successfully",
|
"cache_cleaned": "Cache cleaned successfully",
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
"searching_for": "Searching for packages matching",
|
"searching_for": "Searching for packages matching",
|
||||||
"no_packages_found": "No packages found matching",
|
"no_packages_found": "No packages found matching",
|
||||||
"found_packages": "Found %d packages",
|
"found_packages": "Found %d packages",
|
||||||
"showing_results": "showing first %d results, use --max-results to see more",
|
"showing_results": "showing first %d results, use --max-results to see more",
|
||||||
"package_header": "PACKAGE",
|
"package_header": "PACKAGE",
|
||||||
"version_header": "VERSION",
|
"version_header": "VERSION",
|
||||||
"desc_header": "DESCRIPTION",
|
"desc_header": "DESCRIPTION",
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
"recipe_not_found": "Recipe not found",
|
"recipe_not_found": "Recipe not found",
|
||||||
"package_not_found": "Package not found",
|
"package_not_found": "Package not found",
|
||||||
"build_failed": "Build failed",
|
"build_failed": "Build failed",
|
||||||
"install_failed": "Install failed",
|
"install_failed": "Install failed",
|
||||||
"network_error": "Network error",
|
"network_error": "Network error",
|
||||||
"permission_error": "Permission error",
|
"permission_error": "Permission error",
|
||||||
},
|
},
|
||||||
|
|
||||||
Russian: {
|
Russian: {
|
||||||
// Common
|
// Common
|
||||||
"package": "кулёк",
|
"package": "кулёк",
|
||||||
"packages": "кульки",
|
"packages": "кульки",
|
||||||
"building": "Сборка",
|
"building": "Сборка",
|
||||||
"installing": "Установка",
|
"installing": "Установка",
|
||||||
"removing": "Удаление",
|
"removing": "Удаление",
|
||||||
"searching": "Поиск",
|
"searching": "Поиск",
|
||||||
"downloading": "Скачивание",
|
"downloading": "Скачивание",
|
||||||
"extracting": "Распаковка",
|
"extracting": "Распаковка",
|
||||||
"configuring": "Конфигурация",
|
"configuring": "Конфигурация",
|
||||||
"compiling": "Компиляция",
|
"compiling": "Компиляция",
|
||||||
"completed": "завершено",
|
"completed": "завершено",
|
||||||
"failed": "провалено",
|
"failed": "провалено",
|
||||||
"success": "успешно",
|
"success": "успешно",
|
||||||
"error": "ошибка",
|
"error": "ошибка",
|
||||||
"warning": "предупреждение",
|
"warning": "предупреждение",
|
||||||
"info": "инфо",
|
"info": "инфо",
|
||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
"build_cmd": "Собрать кульок из рецепта",
|
"build_cmd": "Собрать кульок из рецепта",
|
||||||
"install_cmd": "Установить кульки",
|
"install_cmd": "Установить кульки",
|
||||||
"remove_cmd": "Удалить установленные кульки",
|
"remove_cmd": "Удалить установленные кульки",
|
||||||
"list_cmd": "Показать установленные кульки",
|
"list_cmd": "Показать установленные кульки",
|
||||||
"info_cmd": "Информация о кульке",
|
"info_cmd": "Информация о кульке",
|
||||||
"search_cmd": "Поиск кульков",
|
"search_cmd": "Поиск кульков",
|
||||||
"doctor_cmd": "Проверка системы на проблемы",
|
"doctor_cmd": "Проверка системы на проблемы",
|
||||||
"cache_cmd": "Управление кэшем сборки",
|
"cache_cmd": "Управление кэшем сборки",
|
||||||
|
|
||||||
// Status messages
|
// Status messages
|
||||||
"preparing_dirs": "Подготовка директорий",
|
"preparing_dirs": "Подготовка директорий",
|
||||||
"validating_src": "Проверка исходников",
|
"validating_src": "Проверка исходников",
|
||||||
"applying_patches": "Применение патчей",
|
"applying_patches": "Применение патчей",
|
||||||
"creating_archive": "Создание архива кулька",
|
"creating_archive": "Создание архива кулька",
|
||||||
"package_built": "Кулёк %s успешно собран",
|
"package_built": "Кулёк %s успешно собран",
|
||||||
"package_installed": "Установка кульков завершена успешно",
|
"would_install_file": "Установил бы кульок из файла: %s",
|
||||||
"packages_installed": "%d кульков установлено успешно",
|
"would_auto_build": "Собрал бы кульок: %s",
|
||||||
|
"would_install_one": "Установил бы 1 кульок",
|
||||||
|
"would_install_many": "Установил бы %d кульков",
|
||||||
|
"installing_one": "Устанавливаю кульок из %s...",
|
||||||
|
"installing_many": "Устанавливаю %d кульков...",
|
||||||
|
|
||||||
// Progress
|
// Progress
|
||||||
"step": "Шаг",
|
"step": "Шаг",
|
||||||
"of": "из",
|
"of": "из",
|
||||||
"elapsed": "прошло",
|
"elapsed": "прошло",
|
||||||
"remaining": "осталось",
|
"remaining": "осталось",
|
||||||
"eta": "Осталось",
|
"eta": "Осталось",
|
||||||
"steps_per_sec": "шагов/сек",
|
"steps_per_sec": "шагов/сек",
|
||||||
|
|
||||||
// Doctor
|
// Doctor
|
||||||
"system_info": "Информация о системе",
|
"system_info": "Информация о системе",
|
||||||
"build_tools": "Инструменты сборки",
|
"build_tools": "Инструменты сборки",
|
||||||
"directories": "Директории и права",
|
"directories": "Директории и права",
|
||||||
"network": "Сетевое подключение",
|
"network": "Сетевое подключение",
|
||||||
"package_db": "База данных кульков",
|
"package_db": "База данных кульков",
|
||||||
"diagnosis_complete": "Диагностика завершена",
|
"diagnosis_complete": "Диагностика завершена",
|
||||||
"fix_issues": "Если видишь ❌ выше, исправь перед использованием zsvo.",
|
"fix_issues": "Если видишь ❌ выше, исправь перед использованием zsvo.",
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
"cache_info": "Информация о кэше сборки",
|
"cache_info": "Информация о кэше сборки",
|
||||||
"work_dir": "Рабочая директория",
|
"work_dir": "Рабочая директория",
|
||||||
"total_cache": "Общий размер кэша",
|
"total_cache": "Общий размер кэша",
|
||||||
"cache_breakdown": "Детализация кэша",
|
"cache_breakdown": "Детализация кэша",
|
||||||
"download_cache": "Кэш загрузок",
|
"download_cache": "Кэш загрузок",
|
||||||
"built_packages": "Собранные кульки",
|
"built_packages": "Собранные кульки",
|
||||||
"source_files": "Исходники",
|
"source_files": "Исходники",
|
||||||
"staging_files": "Временные файлы",
|
"staging_files": "Временные файлы",
|
||||||
"cached_packages": "Закэшированные кульки",
|
"cached_packages": "Закэшированные кульки",
|
||||||
"cache_cleaned": "Кэш очищен успешно",
|
"cache_cleaned": "Кэш очищен успешно",
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
"searching_for": "Поиск кульков по запросу",
|
"searching_for": "Поиск кульков по запросу",
|
||||||
"no_packages_found": "Кульки не найдены по запросу",
|
"no_packages_found": "Кульки не найдены по запросу",
|
||||||
"found_packages": "Найдено кульков: %d",
|
"found_packages": "Найдено кульков: %d",
|
||||||
"showing_results": "показано первых %d, используй --max-results для больше",
|
"showing_results": "показано первых %d, используй --max-results для больше",
|
||||||
"package_header": "КУЛЁК",
|
"package_header": "КУЛЁК",
|
||||||
"version_header": "ВЕРСИЯ",
|
"version_header": "ВЕРСИЯ",
|
||||||
"desc_header": "ОПИСАНИЕ",
|
"desc_header": "ОПИСАНИЕ",
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
"recipe_not_found": "Рецепт не найден",
|
"recipe_not_found": "Рецепт не найден",
|
||||||
"package_not_found": "Кулёк не найден",
|
"package_not_found": "Кулёк не найден",
|
||||||
"build_failed": "Сборка провалена",
|
"build_failed": "Сборка провалена",
|
||||||
"install_failed": "Установка провалена",
|
"install_failed": "Установка провалена",
|
||||||
"network_error": "Ошибка сети",
|
"network_error": "Ошибка сети",
|
||||||
"permission_error": "Ошибка прав доступа",
|
"permission_error": "Ошибка прав доступа",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// T translates text to current language
|
// T translates text to current language
|
||||||
func T(key string, args ...interface{}) string {
|
func T(key string, args ...interface{}) string {
|
||||||
lang := currentLang
|
lang := currentLang
|
||||||
|
|
||||||
if langTranslations, ok := translations[lang]; ok {
|
if langTranslations, ok := translations[lang]; ok {
|
||||||
if text, ok := langTranslations[key]; ok {
|
if text, ok := langTranslations[key]; ok {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
|
|
@ -200,7 +208,7 @@ func T(key string, args ...interface{}) string {
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to English
|
// Fallback to English
|
||||||
if lang != English {
|
if lang != English {
|
||||||
if langTranslations, ok := translations[English]; ok {
|
if langTranslations, ok := translations[English]; ok {
|
||||||
|
|
@ -212,7 +220,7 @@ func T(key string, args ...interface{}) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final fallback - return key
|
// Final fallback - return key
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return fmt.Sprintf(key, args...)
|
return fmt.Sprintf(key, args...)
|
||||||
|
|
@ -242,7 +250,7 @@ func DetectLanguage() {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check system locale
|
// Check system locale
|
||||||
if lang := os.Getenv("LANG"); lang != "" {
|
if lang := os.Getenv("LANG"); lang != "" {
|
||||||
if strings.Contains(lang, "ru") || strings.Contains(lang, "RU") {
|
if strings.Contains(lang, "ru") || strings.Contains(lang, "RU") {
|
||||||
|
|
@ -250,7 +258,7 @@ func DetectLanguage() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to English
|
// Default to English
|
||||||
SetLanguage(English)
|
SetLanguage(English)
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +268,7 @@ func IsRussian() bool {
|
||||||
return currentLang == Russian
|
return currentLang == Russian
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEnglish returns true if current language is English
|
// IsEnglish returns true if current language is English
|
||||||
func IsEnglish() bool {
|
func IsEnglish() bool {
|
||||||
return currentLang == English
|
return currentLang == English
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,13 @@ func (i *Installer) installPackageTxNoLock(packagePath, txDir string, installedI
|
||||||
return nil, fmt.Errorf("installed infos map cannot be nil")
|
return nil, fmt.Errorf("installed infos map cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate package file exists and is readable
|
||||||
|
if _, err := os.Stat(packagePath); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("package file does not exist: %s", packagePath)
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot access package file %s: %w", packagePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
pkgTxDir, err := os.MkdirTemp(txDir, "pkg-")
|
pkgTxDir, err := os.MkdirTemp(txDir, "pkg-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create package transaction directory: %w", err)
|
return nil, fmt.Errorf("failed to create package transaction directory: %w", err)
|
||||||
|
|
@ -300,8 +307,20 @@ func (i *Installer) installPackageTxNoLock(packagePath, txDir string, installedI
|
||||||
return nil, fmt.Errorf("failed to remove old package %s files: %w", pkgInfo.Name, err)
|
return nil, fmt.Errorf("failed to remove old package %s files: %w", pkgInfo.Name, err)
|
||||||
}
|
}
|
||||||
if err := i.unregisterPackage(pkgInfo.Name); err != nil {
|
if err := i.unregisterPackage(pkgInfo.Name); err != nil {
|
||||||
_ = i.rollbackRemove(backups)
|
// Attempt to rollback, but aggregate errors
|
||||||
return nil, fmt.Errorf("failed to unregister old package %s: %w", pkgInfo.Name, err)
|
var rollbackErrs []string
|
||||||
|
if rbErr := i.rollbackRemove(backups); rbErr != nil {
|
||||||
|
rollbackErrs = append(rollbackErrs, fmt.Sprintf("rollback error: %v", rbErr))
|
||||||
|
}
|
||||||
|
if rbErr := i.registerPackage(oldInfo); rbErr != nil {
|
||||||
|
rollbackErrs = append(rollbackErrs, fmt.Sprintf("reregister error: %v", rbErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
errMsg := fmt.Sprintf("failed to unregister old package %s: %v", pkgInfo.Name, err)
|
||||||
|
if len(rollbackErrs) > 0 {
|
||||||
|
errMsg += fmt.Sprintf(" (rollback errors: %s)", strings.Join(rollbackErrs, "; "))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(errMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
state.replaced = &removeTransactionState{
|
state.replaced = &removeTransactionState{
|
||||||
|
|
@ -314,23 +333,49 @@ func (i *Installer) installPackageTxNoLock(packagePath, txDir string, installedI
|
||||||
backupRoot := filepath.Join(pkgTxDir, "new")
|
backupRoot := filepath.Join(pkgTxDir, "new")
|
||||||
installedPaths, backups, err := i.installFiles(extractRoot, pkgInfo, backupRoot)
|
installedPaths, backups, err := i.installFiles(extractRoot, pkgInfo, backupRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = i.rollbackInstall(installedPaths, backups)
|
// Attempt rollback with error aggregation
|
||||||
if state.replaced != nil {
|
var rollbackErrs []string
|
||||||
_ = i.rollbackRemove(state.replaced.backups)
|
if rbErr := i.rollbackInstall(installedPaths, backups); rbErr != nil {
|
||||||
_ = i.registerPackage(state.replaced.info)
|
rollbackErrs = append(rollbackErrs, fmt.Sprintf("install rollback error: %v", rbErr))
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to install files: %w", err)
|
if state.replaced != nil {
|
||||||
|
if rbErr := i.rollbackRemove(state.replaced.backups); rbErr != nil {
|
||||||
|
rollbackErrs = append(rollbackErrs, fmt.Sprintf("remove rollback error: %v", rbErr))
|
||||||
|
}
|
||||||
|
if rbErr := i.registerPackage(state.replaced.info); rbErr != nil {
|
||||||
|
rollbackErrs = append(rollbackErrs, fmt.Sprintf("reregister error: %v", rbErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errMsg := fmt.Sprintf("failed to install files: %v", err)
|
||||||
|
if len(rollbackErrs) > 0 {
|
||||||
|
errMsg += fmt.Sprintf(" (rollback errors: %s)", strings.Join(rollbackErrs, "; "))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(errMsg)
|
||||||
}
|
}
|
||||||
state.installedPaths = installedPaths
|
state.installedPaths = installedPaths
|
||||||
state.newBackups = backups
|
state.newBackups = backups
|
||||||
|
|
||||||
if err := i.registerPackage(pkgInfo); err != nil {
|
if err := i.registerPackage(pkgInfo); err != nil {
|
||||||
_ = i.rollbackInstall(installedPaths, backups)
|
// Attempt rollback with error aggregation
|
||||||
if state.replaced != nil {
|
var rollbackErrs []string
|
||||||
_ = i.rollbackRemove(state.replaced.backups)
|
if rbErr := i.rollbackInstall(installedPaths, backups); rbErr != nil {
|
||||||
_ = i.registerPackage(state.replaced.info)
|
rollbackErrs = append(rollbackErrs, fmt.Sprintf("install rollback error: %v", rbErr))
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to register package: %w", err)
|
if state.replaced != nil {
|
||||||
|
if rbErr := i.rollbackRemove(state.replaced.backups); rbErr != nil {
|
||||||
|
rollbackErrs = append(rollbackErrs, fmt.Sprintf("remove rollback error: %v", rbErr))
|
||||||
|
}
|
||||||
|
if rbErr := i.registerPackage(state.replaced.info); rbErr != nil {
|
||||||
|
rollbackErrs = append(rollbackErrs, fmt.Sprintf("reregister error: %v", rbErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errMsg := fmt.Sprintf("failed to register package: %v", err)
|
||||||
|
if len(rollbackErrs) > 0 {
|
||||||
|
errMsg += fmt.Sprintf(" (rollback errors: %s)", strings.Join(rollbackErrs, "; "))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(errMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
return state, nil
|
return state, nil
|
||||||
|
|
@ -1274,9 +1319,13 @@ func (i *Installer) withDBLock(exclusive bool, fn func() error) error {
|
||||||
lockType = syscall.LOCK_EX
|
lockType = syscall.LOCK_EX
|
||||||
}
|
}
|
||||||
if err := syscall.Flock(int(lockFile.Fd()), lockType); err != nil {
|
if err := syscall.Flock(int(lockFile.Fd()), lockType); err != nil {
|
||||||
|
lockFile.Close()
|
||||||
return fmt.Errorf("failed to lock package database: %w", err)
|
return fmt.Errorf("failed to lock package database: %w", err)
|
||||||
}
|
}
|
||||||
defer syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN)
|
defer func() {
|
||||||
|
syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN)
|
||||||
|
lockFile.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
return fn()
|
return fn()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# ZSVO Docker Build Script
|
|
||||||
# Linux-only Docker builds
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
IMAGE_NAME="zsvo"
|
|
||||||
VERSION=${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo "dev")}
|
|
||||||
REGISTRY=${REGISTRY:-"localhost:5000"}
|
|
||||||
|
|
||||||
# Functions
|
|
||||||
log_info() {
|
|
||||||
echo -e "${BLUE}[INFO]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_success() {
|
|
||||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_warning() {
|
|
||||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
echo -e "${RED}[ERROR]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_docker() {
|
|
||||||
if ! command -v docker &> /dev/null; then
|
|
||||||
log_error "Docker is not installed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! docker info &> /dev/null; then
|
|
||||||
log_error "Docker daemon is not running"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_success "Docker check passed"
|
|
||||||
}
|
|
||||||
|
|
||||||
build_image() {
|
|
||||||
local tag=${1:-$IMAGE_NAME:$VERSION}
|
|
||||||
|
|
||||||
log_info "Building Docker image..."
|
|
||||||
docker build -t $tag .
|
|
||||||
log_success "Built Docker image: $tag"
|
|
||||||
}
|
|
||||||
|
|
||||||
run_container() {
|
|
||||||
local tag=${1:-$IMAGE_NAME:$VERSION}
|
|
||||||
local command=${2:-"--help"}
|
|
||||||
|
|
||||||
log_info "Running container: $tag"
|
|
||||||
log_info "Command: zsvo $command"
|
|
||||||
docker run --rm -it $tag zsvo $command
|
|
||||||
}
|
|
||||||
|
|
||||||
push_image() {
|
|
||||||
local tag=${1:-$IMAGE_NAME:$VERSION}
|
|
||||||
local remote=${2:-$REGISTRY}
|
|
||||||
|
|
||||||
log_info "Pushing image: $tag"
|
|
||||||
docker tag $tag $remote/$tag
|
|
||||||
docker push $remote/$tag
|
|
||||||
log_success "Image pushed: $remote/$tag"
|
|
||||||
}
|
|
||||||
|
|
||||||
clean_images() {
|
|
||||||
log_info "Cleaning Docker images..."
|
|
||||||
|
|
||||||
docker images $IMAGE_NAME --format "table {{.Repository}}:{{.Tag}}" | grep -v REPOSITORY | while read line; do
|
|
||||||
if [ -n "$line" ]; then
|
|
||||||
docker rmi $line 2>/dev/null || true
|
|
||||||
log_info "Removed: $line"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
log_success "Docker cleanup complete"
|
|
||||||
}
|
|
||||||
|
|
||||||
show_info() {
|
|
||||||
log_info "ZSVO Docker Information:"
|
|
||||||
echo " Image: $IMAGE_NAME"
|
|
||||||
echo " Version: $VERSION"
|
|
||||||
echo " Registry: $REGISTRY"
|
|
||||||
echo ""
|
|
||||||
echo "Available images:"
|
|
||||||
docker images $IMAGE_NAME 2>/dev/null | head -10 || echo " No images found"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main script
|
|
||||||
case "${1:-help}" in
|
|
||||||
"build")
|
|
||||||
build_image "$2"
|
|
||||||
;;
|
|
||||||
"run")
|
|
||||||
run_container "$2" "$3"
|
|
||||||
;;
|
|
||||||
"push")
|
|
||||||
push_image "$2" "$3"
|
|
||||||
;;
|
|
||||||
"clean")
|
|
||||||
clean_images
|
|
||||||
;;
|
|
||||||
"info")
|
|
||||||
show_info
|
|
||||||
;;
|
|
||||||
"help"|*)
|
|
||||||
echo "ZSVO Docker Build Script"
|
|
||||||
echo ""
|
|
||||||
echo "Usage: $0 [command] [options]"
|
|
||||||
echo ""
|
|
||||||
echo "Commands:"
|
|
||||||
echo " build [tag] Build Docker image"
|
|
||||||
echo " run [tag] [command] Run container"
|
|
||||||
echo " push [tag] [registry] Push image to registry"
|
|
||||||
echo " clean Clean Docker images"
|
|
||||||
echo " info Show Docker information"
|
|
||||||
echo " help Show this help"
|
|
||||||
echo ""
|
|
||||||
echo "Examples:"
|
|
||||||
echo " $0 build"
|
|
||||||
echo " $0 build zsvo:latest"
|
|
||||||
echo " $0 run zsvo:latest --help"
|
|
||||||
echo " $0 push zsvo:latest my-registry.com"
|
|
||||||
echo ""
|
|
||||||
echo "Environment Variables:"
|
|
||||||
echo " VERSION Override version"
|
|
||||||
echo " REGISTRY Docker registry"
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
# Test environment for ZSVO on Debian 13
|
|
||||||
FROM debian:13
|
|
||||||
|
|
||||||
# Install basic build tools
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
build-essential \
|
|
||||||
cmake \
|
|
||||||
git \
|
|
||||||
make \
|
|
||||||
pkg-config \
|
|
||||||
wget \
|
|
||||||
curl \
|
|
||||||
golang-go \
|
|
||||||
sudo \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Create zsvo user with sudo access
|
|
||||||
RUN useradd -m -s /bin/bash -G sudo zsvo
|
|
||||||
RUN echo "zsvo ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /home/zsvo
|
|
||||||
|
|
||||||
# Copy zsvo binary and install script
|
|
||||||
COPY zsvo /usr/local/bin/zsvo
|
|
||||||
COPY install-script.sh /tmp/install-script.sh
|
|
||||||
RUN chmod +x /usr/local/bin/zsvo /tmp/install-script.sh
|
|
||||||
|
|
||||||
# Create necessary directories with proper permissions
|
|
||||||
RUN mkdir -p /tmp/pkg-work /home/zsvo/packages /var/lib/pkgdb && \
|
|
||||||
chown -R zsvo:zsvo /tmp/pkg-work /home/zsvo/packages /var/lib/pkgdb
|
|
||||||
|
|
||||||
# Switch to zsvo user
|
|
||||||
USER zsvo
|
|
||||||
|
|
||||||
# Set environment
|
|
||||||
ENV PATH=/usr/local/bin:/usr/bin:/bin
|
|
||||||
ENV HOME=/home/zsvo
|
|
||||||
|
|
||||||
# Test command
|
|
||||||
CMD ["/bin/bash"]
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "🚀 Запускаем ZSVO в Docker..."
|
|
||||||
|
|
||||||
# Проверяем что Docker запущен
|
|
||||||
if ! docker ps > /dev/null 2>&1; then
|
|
||||||
echo "❌ Docker не запущен. Запускаем Docker Desktop..."
|
|
||||||
open -a Docker
|
|
||||||
echo "⏳ Ждем запуска Docker..."
|
|
||||||
sleep 10
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Запускаем контейнер
|
|
||||||
docker run -it --rm \
|
|
||||||
-v $(pwd):/workspace \
|
|
||||||
-w /workspace \
|
|
||||||
--name zsvo-test \
|
|
||||||
golang:1.23 bash -c "
|
|
||||||
echo '=== УСТАНАВЛИВАЕМ ЗАВИСИМОСТИ ===' &&
|
|
||||||
apt-get update -qq &&
|
|
||||||
apt-get install -y -qq build-essential git curl wget cmake meson pkg-config python3 tar xz-utils &&
|
|
||||||
|
|
||||||
echo '=== СОБИРАЕМ ZSVO ===' &&
|
|
||||||
go build -o zsvo . &&
|
|
||||||
|
|
||||||
echo '=== ГОТОВО! ТЕРМИНАЛ ZSVO ===' &&
|
|
||||||
echo 'Доступные команды:' &&
|
|
||||||
echo ' ./zsvo doctor' &&
|
|
||||||
echo ' ./zsvo install htop --dry-run' &&
|
|
||||||
echo ' ./zsvo install neovim --dry-run' &&
|
|
||||||
echo ' ./zsvo search python' &&
|
|
||||||
echo ' ./zsvo lang ru' &&
|
|
||||||
echo ' ./zsvo cache info' &&
|
|
||||||
echo '' &&
|
|
||||||
echo 'Для выхода: exit' &&
|
|
||||||
echo '' &&
|
|
||||||
bash
|
|
||||||
"
|
|
||||||
|
|
||||||
echo "✅ ZSVO Docker контейнер завершен"
|
|
||||||
Loading…
Reference in a new issue