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:
itexpert228 2026-03-14 19:32:54 +03:00
parent f9d0fa905d
commit fc315b1c2c
No known key found for this signature in database
12 changed files with 622 additions and 659 deletions

View file

@ -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
View file

@ -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)"

View file

@ -46,7 +46,7 @@ var InstallCmd = &cobra.Command{
status.PrintInfo(fmt.Sprintf("Work directory: %s", workDir))
status.PrintInfo(fmt.Sprintf("Auto-source: %t", autoSource))
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()
}
@ -68,7 +68,7 @@ var InstallCmd = &cobra.Command{
if dryRun {
status := ui.NewStatusBar("", 1)
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)
continue
@ -84,7 +84,7 @@ var InstallCmd = &cobra.Command{
if dryRun {
status := ui.NewStatusBar("", 1)
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) // для демонстрации
continue
}
@ -100,17 +100,17 @@ var InstallCmd = &cobra.Command{
if dryRun {
status := ui.NewStatusBar("", 1)
status.SetTheme("neon")
status.PrintInfo(i18n.T("Would install 1 package"))
status.PrintInfo(i18n.T("would_install_one"))
} else {
fmt.Printf(i18n.T("Installing package from %s...")+"\n", installTargets[0])
fmt.Printf(i18n.T("installing_one")+"\n", installTargets[0])
}
} else {
if dryRun {
status := ui.NewStatusBar("", 1)
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 {
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")
}
const maxAutoBuildDepth = 16
const maxAutoBuildDepth = 3 // Уменьшим для тестов
type autoBuildSession struct {
workDir string
@ -276,8 +276,19 @@ func (s *autoBuildSession) buildPackageWithFallback(requestName string, asBuildD
if !allowFailure {
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)
return "", nil
fmt.Printf("Retrying build for %s after auto-installing dependencies...\n", requestName)
// В 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)
@ -290,6 +301,15 @@ func (s *autoBuildSession) buildPackageWithFallback(requestName string, asBuildD
}
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
for attempt := 0; attempt < 2; attempt++ {
bar := newProgressUI(rcp.Name)
@ -325,10 +345,47 @@ func (s *autoBuildSession) buildPackageWithFallback(requestName string, asBuildD
return "", err
}
}
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 {
dep = normalizePackageName(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)
packagePath, err := s.buildPackageWithFallback(dep, true, false, append(stack, dep))
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)
}
@ -443,6 +501,7 @@ func inferMissingBuildDeps(err error) []string {
// Detect missing CMake
if strings.Contains(lowerText, "cmake") &&
!strings.Contains(lowerText, "lua 5.1") &&
(strings.Contains(lowerText, "not found") ||
strings.Contains(lowerText, "command not found") ||
strings.Contains(lowerText, "cmake: command not found") ||
@ -463,23 +522,23 @@ func inferMissingBuildDeps(err error) []string {
found["git"] = struct{}{}
}
// Detect missing make
if strings.Contains(lowerText, "make") &&
(strings.Contains(lowerText, "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, "no make") ||
strings.Contains(lowerText, "could not find make")) {
found["make"] = struct{}{}
}
// Detect missing make - but don't auto-add it as it's usually a build system issue
// if strings.Contains(lowerText, "make") &&
// (strings.Contains(lowerText, "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, "no make") ||
// strings.Contains(lowerText, "could not find make")) {
// found["make"] = struct{}{}
// }
// 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, "cmake generate step failed") ||
strings.Contains(lowerText, "cmake error") ||
strings.Contains(lowerText, "could not load cache") {
strings.Contains(lowerText, "could not load cache")) {
found["cmake"] = struct{}{}
}
@ -505,6 +564,225 @@ func inferMissingBuildDeps(err error) []string {
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 {
tool = normalizePackageName(tool)
if tool == "" || allDigits(tool) || len(tool) < 2 {
@ -536,30 +814,22 @@ func mapToolToSourcePackage(tool string) string {
return "ninja-build"
case "python":
return "python3"
case "python3":
return "python3"
case "lua":
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":
return "autoconf"
case "automake":
return "automake"
case "libtool":
return "libtool"
case "xzcat":
return "xz-utils"
case "ld":
return "binutils"
case "git":
return "git"
case "cmake":
return "cmake"
case "pkgconf":
return "pkgconf"
case "flex":
@ -651,6 +921,20 @@ func toolAlreadyAvailable(dep string) bool {
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]
if len(commands) == 0 {
commands = []string{dep}
@ -689,7 +973,10 @@ func autoRecipeFromDebian(src *debian.SourceInfo) *recipe.Recipe {
},
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 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 Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then make -j${jobs} ${ZSVO_MAKE_FLAGS}; fi",
},

View file

@ -27,6 +27,6 @@ if [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then
exit 0
fi
# Fallback - create minimal structure in PKGDIR
# Fallback - create minimal structure
echo "Warning: No standard build system found, installing common directories"
mkdir -p "$PKGDIR/usr/bin" "$PKGDIR/usr/lib" "$PKGDIR/usr/include"

View file

@ -1,6 +1,7 @@
package builder
import (
"context"
"fmt"
"log"
"os"
@ -9,6 +10,7 @@ import (
"runtime"
"strings"
"sync"
"time"
"zsvo/pkg/fetcher"
"zsvo/pkg/packager"
@ -230,9 +232,9 @@ func (b *Builder) buildEnvironment(stagingDir string) []string {
env := os.Environ()
// Add standard build environment variables
env = append(env, fmt.Sprintf("DESTDIR=%s", stagingDir))
env = append(env, fmt.Sprintf("PREFIX=/usr"))
env = append(env, fmt.Sprintf("PKGDIR=%s", stagingDir))
env = append(env, "DESTDIR="+stagingDir)
env = append(env, "PREFIX=/usr")
env = append(env, "PKGDIR="+stagingDir)
// Add parallel build variable
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.Dir = workDir
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)
cmd.Dir = workDir
cmd.Env = env
output, err := cmd.CombinedOutput()
if err != nil {
details := tailOutput(string(output), 20)
@ -269,6 +277,13 @@ func (b *Builder) executeCommand(workDir, command string, env []string) error {
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.Stderr = os.Stderr
@ -598,7 +613,6 @@ func (b *Builder) validateSourceFiles(recipe *recipe.Recipe, sourceDir string) e
return nil
}
// validateCommand performs basic security checks on build commands
func (b *Builder) validateCommand(command string) error {
// List of dangerous patterns to block
dangerousPatterns := []string{
@ -609,17 +623,28 @@ func (b *Builder) validateCommand(command string) error {
"chown root",
"sudo ",
"su ",
"passwd",
"curl | sh",
"wget | sh",
"eval $(",
"sh -c $(",
"bash -c $(",
"> /dev/sda",
"> /dev/hda",
"mkfs",
"format",
"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)
@ -650,6 +675,17 @@ func (b *Builder) validateCommand(command string) error {
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)
if len(command) > 100 {
log.Printf("Command validation passed (truncated): %s...", command[:100])

View file

@ -33,6 +33,7 @@ type SourceInfo struct {
UpstreamVersion string
Suite string
Component string
BuildDepends []string // Build dependencies from DSC file
}
// Resolver queries Debian source metadata over HTTP.
@ -126,6 +127,7 @@ func (r *Resolver) ResolveSource(pkg string) (*SourceInfo, error) {
UpstreamVersion: normalizeUpstreamVersion(record.Version),
Suite: suite,
Component: component,
BuildDepends: record.BuildDepends,
}, nil
}
}
@ -139,12 +141,13 @@ func (r *Resolver) ResolveSource(pkg string) (*SourceInfo, error) {
}
type sourceRecord struct {
Package string
Version string
Directory string
DSCName string
DSCSHA256 string
Binaries []string
Package string
Version string
Directory string
DSCName string
DSCSHA256 string
Binaries []string
BuildDepends []string
}
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), "/")
return sourceRecord{
Package: pkg,
Version: ver,
Directory: dir,
DSCName: dscName,
DSCSHA256: dscHash,
Binaries: parseCommaSeparatedField(fields["Binary"]),
Package: pkg,
Version: ver,
Directory: dir,
DSCName: dscName,
DSCSHA256: dscHash,
Binaries: parseCommaSeparatedField(fields["Binary"]),
BuildDepends: parseCommaSeparatedField(fields["Build-Depends"]),
}, nil
}

View file

@ -2,6 +2,7 @@ package fetcher
import (
"bufio"
"context"
"crypto/sha256"
"fmt"
"io"
@ -11,6 +12,7 @@ import (
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/mholt/archiver/v3"
"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)
}
// Download file
resp, err := http.Get(url)
// Download file with timeout
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 {
return "", fmt.Errorf("failed to download %s: %w", url, err)
}

View file

@ -19,172 +19,180 @@ var currentLang = English
var translations = map[Language]map[string]string{
English: {
// 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",
"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
"build_cmd": "Build a package from recipe",
"install_cmd": "Install package(s)",
"remove_cmd": "Remove installed package(s)",
"list_cmd": "List installed packages",
"info_cmd": "Show package information",
"search_cmd": "Search for packages",
"doctor_cmd": "Check system for potential issues",
"cache_cmd": "Manage build cache",
"build_cmd": "Build a package from recipe",
"install_cmd": "Install package(s)",
"remove_cmd": "Remove installed package(s)",
"list_cmd": "List installed packages",
"info_cmd": "Show package information",
"search_cmd": "Search for packages",
"doctor_cmd": "Check system for potential issues",
"cache_cmd": "Manage build cache",
// Status messages
"preparing_dirs": "Preparing directories",
"validating_src": "Validating source files",
"applying_patches": "Applying recipe patches",
"creating_archive": "Creating package archive",
"package_built": "Package %s built successfully",
"package_installed": "Package installation completed successfully",
"packages_installed": "%d packages installed successfully",
"preparing_dirs": "Preparing directories",
"validating_src": "Validating source files",
"applying_patches": "Applying recipe patches",
"creating_archive": "Creating package archive",
"package_built": "Package %s built successfully",
"would_install_file": "Would install package from file: %s",
"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
"step": "Step",
"of": "of",
"elapsed": "elapsed",
"remaining": "remaining",
"eta": "ETA",
"steps_per_sec": "steps/s",
"step": "Step",
"of": "of",
"elapsed": "elapsed",
"remaining": "remaining",
"eta": "ETA",
"steps_per_sec": "steps/s",
// Doctor
"system_info": "System Information",
"build_tools": "Build Tools",
"directories": "Directories & Permissions",
"network": "Network Connectivity",
"package_db": "Package Database",
"system_info": "System Information",
"build_tools": "Build Tools",
"directories": "Directories & Permissions",
"network": "Network Connectivity",
"package_db": "Package Database",
"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_info": "Build Cache Information",
"work_dir": "Work directory",
"total_cache": "Total cache size",
"cache_info": "Build Cache Information",
"work_dir": "Work directory",
"total_cache": "Total cache size",
"cache_breakdown": "Cache breakdown",
"download_cache": "Download cache",
"built_packages": "Built packages",
"source_files": "Source files",
"staging_files": "Staging files",
"download_cache": "Download cache",
"built_packages": "Built packages",
"source_files": "Source files",
"staging_files": "Staging files",
"cached_packages": "Cached packages",
"cache_cleaned": "Cache cleaned successfully",
"cache_cleaned": "Cache cleaned successfully",
// Search
"searching_for": "Searching for packages matching",
"searching_for": "Searching for packages matching",
"no_packages_found": "No packages found matching",
"found_packages": "Found %d packages",
"showing_results": "showing first %d results, use --max-results to see more",
"package_header": "PACKAGE",
"version_header": "VERSION",
"desc_header": "DESCRIPTION",
"found_packages": "Found %d packages",
"showing_results": "showing first %d results, use --max-results to see more",
"package_header": "PACKAGE",
"version_header": "VERSION",
"desc_header": "DESCRIPTION",
// Errors
"recipe_not_found": "Recipe not found",
"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",
"build_failed": "Build failed",
"install_failed": "Install failed",
"network_error": "Network error",
"permission_error": "Permission error",
},
Russian: {
// Common
"package": "кулёк",
"packages": "кульки",
"building": "Сборка",
"installing": "Установка",
"removing": "Удаление",
"searching": "Поиск",
"downloading": "Скачивание",
"extracting": "Распаковка",
"configuring": "Конфигурация",
"compiling": "Компиляция",
"completed": "завершено",
"failed": "провалено",
"success": "успешно",
"error": "ошибка",
"warning": "предупреждение",
"info": "инфо",
"package": "кулёк",
"packages": "кульки",
"building": "Сборка",
"installing": "Установка",
"removing": "Удаление",
"searching": "Поиск",
"downloading": "Скачивание",
"extracting": "Распаковка",
"configuring": "Конфигурация",
"compiling": "Компиляция",
"completed": "завершено",
"failed": "провалено",
"success": "успешно",
"error": "ошибка",
"warning": "предупреждение",
"info": "инфо",
// Commands
"build_cmd": "Собрать кульок из рецепта",
"install_cmd": "Установить кульки",
"remove_cmd": "Удалить установленные кульки",
"list_cmd": "Показать установленные кульки",
"info_cmd": "Информация о кульке",
"search_cmd": "Поиск кульков",
"doctor_cmd": "Проверка системы на проблемы",
"cache_cmd": "Управление кэшем сборки",
"build_cmd": "Собрать кульок из рецепта",
"install_cmd": "Установить кульки",
"remove_cmd": "Удалить установленные кульки",
"list_cmd": "Показать установленные кульки",
"info_cmd": "Информация о кульке",
"search_cmd": "Поиск кульков",
"doctor_cmd": "Проверка системы на проблемы",
"cache_cmd": "Управление кэшем сборки",
// Status messages
"preparing_dirs": "Подготовка директорий",
"validating_src": "Проверка исходников",
"applying_patches": "Применение патчей",
"creating_archive": "Создание архива кулька",
"package_built": "Кулёк %s успешно собран",
"package_installed": "Установка кульков завершена успешно",
"packages_installed": "%d кульков установлено успешно",
"preparing_dirs": "Подготовка директорий",
"validating_src": "Проверка исходников",
"applying_patches": "Применение патчей",
"creating_archive": "Создание архива кулька",
"package_built": "Кулёк %s успешно собран",
"would_install_file": "Установил бы кульок из файла: %s",
"would_auto_build": "Собрал бы кульок: %s",
"would_install_one": "Установил бы 1 кульок",
"would_install_many": "Установил бы %d кульков",
"installing_one": "Устанавливаю кульок из %s...",
"installing_many": "Устанавливаю %d кульков...",
// Progress
"step": "Шаг",
"of": "из",
"elapsed": "прошло",
"remaining": "осталось",
"eta": "Осталось",
"steps_per_sec": "шагов/сек",
"step": "Шаг",
"of": "из",
"elapsed": "прошло",
"remaining": "осталось",
"eta": "Осталось",
"steps_per_sec": "шагов/сек",
// Doctor
"system_info": "Информация о системе",
"build_tools": "Инструменты сборки",
"directories": "Директории и права",
"network": "Сетевое подключение",
"package_db": "База данных кульков",
"system_info": "Информация о системе",
"build_tools": "Инструменты сборки",
"directories": "Директории и права",
"network": "Сетевое подключение",
"package_db": "База данных кульков",
"diagnosis_complete": "Диагностика завершена",
"fix_issues": "Если видишь ❌ выше, исправь перед использованием zsvo.",
"fix_issues": "Если видишь ❌ выше, исправь перед использованием zsvo.",
// Cache
"cache_info": "Информация о кэше сборки",
"work_dir": "Рабочая директория",
"total_cache": "Общий размер кэша",
"cache_info": "Информация о кэше сборки",
"work_dir": "Рабочая директория",
"total_cache": "Общий размер кэша",
"cache_breakdown": "Детализация кэша",
"download_cache": "Кэш загрузок",
"built_packages": "Собранные кульки",
"source_files": "Исходники",
"staging_files": "Временные файлы",
"download_cache": "Кэш загрузок",
"built_packages": "Собранные кульки",
"source_files": "Исходники",
"staging_files": "Временные файлы",
"cached_packages": "Закэшированные кульки",
"cache_cleaned": "Кэш очищен успешно",
"cache_cleaned": "Кэш очищен успешно",
// Search
"searching_for": "Поиск кульков по запросу",
"searching_for": "Поиск кульков по запросу",
"no_packages_found": "Кульки не найдены по запросу",
"found_packages": "Найдено кульков: %d",
"showing_results": "показано первых %d, используй --max-results для больше",
"package_header": "КУЛЁК",
"version_header": "ВЕРСИЯ",
"desc_header": "ОПИСАНИЕ",
"found_packages": "Найдено кульков: %d",
"showing_results": "показано первых %d, используй --max-results для больше",
"package_header": "КУЛЁК",
"version_header": "ВЕРСИЯ",
"desc_header": "ОПИСАНИЕ",
// Errors
"recipe_not_found": "Рецепт не найден",
"recipe_not_found": "Рецепт не найден",
"package_not_found": "Кулёк не найден",
"build_failed": "Сборка провалена",
"install_failed": "Установка провалена",
"network_error": "Ошибка сети",
"permission_error": "Ошибка прав доступа",
"build_failed": "Сборка провалена",
"install_failed": "Установка провалена",
"network_error": "Ошибка сети",
"permission_error": "Ошибка прав доступа",
},
}

View file

@ -255,6 +255,13 @@ func (i *Installer) installPackageTxNoLock(packagePath, txDir string, installedI
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-")
if err != nil {
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)
}
if err := i.unregisterPackage(pkgInfo.Name); err != nil {
_ = i.rollbackRemove(backups)
return nil, fmt.Errorf("failed to unregister old package %s: %w", pkgInfo.Name, err)
// Attempt to rollback, but aggregate errors
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{
@ -314,23 +333,49 @@ func (i *Installer) installPackageTxNoLock(packagePath, txDir string, installedI
backupRoot := filepath.Join(pkgTxDir, "new")
installedPaths, backups, err := i.installFiles(extractRoot, pkgInfo, backupRoot)
if err != nil {
_ = i.rollbackInstall(installedPaths, backups)
if state.replaced != nil {
_ = i.rollbackRemove(state.replaced.backups)
_ = i.registerPackage(state.replaced.info)
// Attempt rollback with error aggregation
var rollbackErrs []string
if rbErr := i.rollbackInstall(installedPaths, backups); rbErr != nil {
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.newBackups = backups
if err := i.registerPackage(pkgInfo); err != nil {
_ = i.rollbackInstall(installedPaths, backups)
if state.replaced != nil {
_ = i.rollbackRemove(state.replaced.backups)
_ = i.registerPackage(state.replaced.info)
// Attempt rollback with error aggregation
var rollbackErrs []string
if rbErr := i.rollbackInstall(installedPaths, backups); rbErr != nil {
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
@ -1274,9 +1319,13 @@ func (i *Installer) withDBLock(exclusive bool, fn func() error) error {
lockType = syscall.LOCK_EX
}
if err := syscall.Flock(int(lockFile.Fd()), lockType); err != nil {
lockFile.Close()
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()
}

View file

@ -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

View file

@ -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"]

View file

@ -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 контейнер завершен"