Compare commits
No commits in common. "fc315b1c2c2607bdc898178b259779eb89479cfc" and "d2bab3b0d1d6dc0959b67f642f02f001058892b4" have entirely different histories.
fc315b1c2c
...
d2bab3b0d1
13 changed files with 657 additions and 635 deletions
59
Dockerfile
Normal file
59
Dockerfile
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# 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
Normal file
149
Makefile
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
# 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)"
|
||||||
14
PREBUILT.md
14
PREBUILT.md
|
|
@ -1,14 +0,0 @@
|
||||||
# Pre-built packages repository
|
|
||||||
# For users who want to install packages without building from source
|
|
||||||
|
|
||||||
## How to use:
|
|
||||||
# ./zsvo install --repo-url https://github.com/yourname/zsvo-prebuilt neovim
|
|
||||||
|
|
||||||
## Available packages:
|
|
||||||
- neovim-0.10.4.pkg.tar.zst # Latest neovim (built with all dependencies)
|
|
||||||
- htop-3.4.1.pkg.tar.zst # Latest htop
|
|
||||||
- cmake-3.28.3.pkg.tar.zst # Latest cmake
|
|
||||||
- git-2.43.0.pkg.tar.zst # Latest git
|
|
||||||
|
|
||||||
## Building packages:
|
|
||||||
# Use scripts/build-prebuilt.sh to create packages for this repository
|
|
||||||
370
cmd/install.go
370
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("Auto-resolve-deps: enabled (default)")
|
status.PrintInfo(fmt.Sprintf("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_file"), target))
|
status.PrintInfo(fmt.Sprintf(i18n.T("Would install package from file: %s"), 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"), target))
|
status.PrintInfo(fmt.Sprintf(i18n.T("Would auto-build package: %s"), 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_one"))
|
status.PrintInfo(i18n.T("Would install 1 package"))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(i18n.T("installing_one")+"\n", installTargets[0])
|
fmt.Printf(i18n.T("Installing package from %s...")+"\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_many"), len(installTargets)))
|
status.PrintInfo(fmt.Sprintf(i18n.T("Would install %d packages"), len(installTargets)))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(i18n.T("installing_many")+"\n", len(installTargets))
|
fmt.Printf(i18n.T("Installing %d packages...")+"\n", len(installTargets))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,7 +184,7 @@ func looksLikeFilePath(target string) bool {
|
||||||
strings.HasSuffix(target, ".zov")
|
strings.HasSuffix(target, ".zov")
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxAutoBuildDepth = 3 // Уменьшим для тестов
|
const maxAutoBuildDepth = 16
|
||||||
|
|
||||||
type autoBuildSession struct {
|
type autoBuildSession struct {
|
||||||
workDir string
|
workDir string
|
||||||
|
|
@ -276,19 +276,8 @@ 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("Retrying build for %s after auto-installing dependencies...\n", requestName)
|
fmt.Printf("Warning: failed to resolve source for %s: %v (continuing anyway)\n", requestName, err)
|
||||||
|
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)
|
||||||
|
|
@ -301,15 +290,6 @@ 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)
|
||||||
|
|
@ -345,47 +325,10 @@ 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 == "" {
|
||||||
|
|
@ -403,8 +346,7 @@ 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 {
|
||||||
fmt.Printf("Warning: failed to build dependency %s: %v (continuing anyway)\n", dep, err)
|
return fmt.Errorf("failed to build dependency %s: %w", dep, err)
|
||||||
// Continue anyway instead of failing
|
|
||||||
}
|
}
|
||||||
return s.installBuildDependency(dep, packagePath)
|
return s.installBuildDependency(dep, packagePath)
|
||||||
}
|
}
|
||||||
|
|
@ -501,7 +443,6 @@ 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") ||
|
||||||
|
|
@ -522,23 +463,23 @@ func inferMissingBuildDeps(err error) []string {
|
||||||
found["git"] = struct{}{}
|
found["git"] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect missing make - but don't auto-add it as it's usually a build system issue
|
// Detect missing make
|
||||||
// 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, "lua 5.1") && (strings.Contains(lowerText, "cannot find source file") ||
|
if 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{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -564,225 +505,6 @@ 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 {
|
||||||
|
|
@ -814,22 +536,30 @@ 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":
|
||||||
|
|
@ -921,20 +651,6 @@ 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}
|
||||||
|
|
@ -973,10 +689,7 @@ 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",
|
||||||
"# Download missing dependencies automatically",
|
"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}; " +
|
||||||
"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",
|
||||||
},
|
},
|
||||||
|
|
@ -1100,7 +813,6 @@ func buildFailureHint(pkgName string, err error) string {
|
||||||
hints,
|
hints,
|
||||||
fmt.Sprintf("Hint: missing build dependencies detected: %s.", strings.Join(missingDeps, ", ")),
|
fmt.Sprintf("Hint: missing build dependencies detected: %s.", strings.Join(missingDeps, ", ")),
|
||||||
"Hint: добавь рецепты для этих пакетов (или алиасы к Debian source), затем повтори `zsvo install`.",
|
"Hint: добавь рецепты для этих пакетов (или алиасы к Debian source), затем повтори `zsvo install`.",
|
||||||
"Hint: или используйте готовые пакеты: ./zsvo install --repo-url https://github.com/yourname/zsvo-prebuilt neovim",
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if strings.Contains(text, "on systems using dpkg and apt, try: \"apt-get install package\"") {
|
if strings.Contains(text, "on systems using dpkg and apt, try: \"apt-get install package\"") {
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,6 @@ if [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fallback - create minimal structure
|
# Fallback - create minimal structure in PKGDIR
|
||||||
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,7 +1,6 @@
|
||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -10,7 +9,6 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"zsvo/pkg/fetcher"
|
"zsvo/pkg/fetcher"
|
||||||
"zsvo/pkg/packager"
|
"zsvo/pkg/packager"
|
||||||
|
|
@ -232,9 +230,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, "DESTDIR="+stagingDir)
|
env = append(env, fmt.Sprintf("DESTDIR=%s", stagingDir))
|
||||||
env = append(env, "PREFIX=/usr")
|
env = append(env, fmt.Sprintf("PREFIX=/usr"))
|
||||||
env = append(env, "PKGDIR="+stagingDir)
|
env = append(env, fmt.Sprintf("PKGDIR=%s", 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()))
|
||||||
|
|
@ -258,14 +256,8 @@ 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 {
|
|
||||||
// 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)
|
if b.quiet {
|
||||||
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)
|
||||||
|
|
@ -277,13 +269,6 @@ 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
|
||||||
|
|
||||||
|
|
@ -613,6 +598,7 @@ 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{
|
||||||
|
|
@ -623,28 +609,17 @@ func (b *Builder) validateCommand(command string) error {
|
||||||
"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)
|
||||||
|
|
@ -675,17 +650,6 @@ 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,7 +33,6 @@ 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.
|
||||||
|
|
@ -127,7 +126,6 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,13 +139,12 @@ 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) {
|
||||||
|
|
@ -293,13 +290,12 @@ 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,7 +2,6 @@ package fetcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -12,7 +11,6 @@ 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"
|
||||||
|
|
@ -61,16 +59,8 @@ 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 with timeout
|
// Download file
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
resp, err := http.Get(url)
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
256
pkg/i18n/i18n.go
256
pkg/i18n/i18n.go
|
|
@ -19,180 +19,172 @@ 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",
|
||||||
"would_install_file": "Would install package from file: %s",
|
"package_installed": "Package installation completed successfully",
|
||||||
"would_auto_build": "Would auto-build package: %s",
|
"packages_installed": "%d packages installed successfully",
|
||||||
"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 успешно собран",
|
||||||
"would_install_file": "Установил бы кульок из файла: %s",
|
"package_installed": "Установка кульков завершена успешно",
|
||||||
"would_auto_build": "Собрал бы кульок: %s",
|
"packages_installed": "%d кульков установлено успешно",
|
||||||
"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": "Ошибка прав доступа",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -255,13 +255,6 @@ 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)
|
||||||
|
|
@ -307,20 +300,8 @@ 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 {
|
||||||
// Attempt to rollback, but aggregate errors
|
_ = i.rollbackRemove(backups)
|
||||||
var rollbackErrs []string
|
return nil, fmt.Errorf("failed to unregister old package %s: %w", pkgInfo.Name, err)
|
||||||
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{
|
||||||
|
|
@ -333,49 +314,23 @@ 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 {
|
||||||
// Attempt rollback with error aggregation
|
_ = i.rollbackInstall(installedPaths, backups)
|
||||||
var rollbackErrs []string
|
|
||||||
if rbErr := i.rollbackInstall(installedPaths, backups); rbErr != nil {
|
|
||||||
rollbackErrs = append(rollbackErrs, fmt.Sprintf("install rollback error: %v", rbErr))
|
|
||||||
}
|
|
||||||
if state.replaced != nil {
|
if state.replaced != nil {
|
||||||
if rbErr := i.rollbackRemove(state.replaced.backups); rbErr != nil {
|
_ = i.rollbackRemove(state.replaced.backups)
|
||||||
rollbackErrs = append(rollbackErrs, fmt.Sprintf("remove rollback error: %v", rbErr))
|
_ = i.registerPackage(state.replaced.info)
|
||||||
}
|
|
||||||
if rbErr := i.registerPackage(state.replaced.info); rbErr != nil {
|
|
||||||
rollbackErrs = append(rollbackErrs, fmt.Sprintf("reregister error: %v", rbErr))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return nil, fmt.Errorf("failed to install files: %w", err)
|
||||||
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 {
|
||||||
// Attempt rollback with error aggregation
|
_ = i.rollbackInstall(installedPaths, backups)
|
||||||
var rollbackErrs []string
|
|
||||||
if rbErr := i.rollbackInstall(installedPaths, backups); rbErr != nil {
|
|
||||||
rollbackErrs = append(rollbackErrs, fmt.Sprintf("install rollback error: %v", rbErr))
|
|
||||||
}
|
|
||||||
if state.replaced != nil {
|
if state.replaced != nil {
|
||||||
if rbErr := i.rollbackRemove(state.replaced.backups); rbErr != nil {
|
_ = i.rollbackRemove(state.replaced.backups)
|
||||||
rollbackErrs = append(rollbackErrs, fmt.Sprintf("remove rollback error: %v", rbErr))
|
_ = i.registerPackage(state.replaced.info)
|
||||||
}
|
|
||||||
if rbErr := i.registerPackage(state.replaced.info); rbErr != nil {
|
|
||||||
rollbackErrs = append(rollbackErrs, fmt.Sprintf("reregister error: %v", rbErr))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return nil, fmt.Errorf("failed to register package: %w", err)
|
||||||
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
|
||||||
|
|
@ -1319,13 +1274,9 @@ 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 func() {
|
defer syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN)
|
||||||
syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN)
|
|
||||||
lockFile.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return fn()
|
return fn()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
142
scripts/docker.sh
Executable file
142
scripts/docker.sh
Executable file
|
|
@ -0,0 +1,142 @@
|
||||||
|
#!/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
|
||||||
41
test-env.dockerfile
Normal file
41
test-env.dockerfile
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# 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"]
|
||||||
40
zsvo-docker.sh
Executable file
40
zsvo-docker.sh
Executable file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/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