From 9cdb3d29aa0a5424d2d631f9563537aa768be17e Mon Sep 17 00:00:00 2001 From: EnumDev Date: Fri, 25 Apr 2025 14:13:12 +0300 Subject: [PATCH] Reallow direct source package installation using 'bpm install' --- Makefile | 6 ++- src/bpmlib/compilation.go | 80 ++++++++++++++++++++++++++++++++++++--- src/bpmlib/operations.go | 34 ++++++++++++++++- src/bpmlib/packages.go | 19 +++++----- src/bpmlib/tarball.go | 24 +++++++++++- 5 files changed, 143 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 1b4a678..7c8a82e 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,13 @@ SYSCONFDIR := $(PREFIX)/etc # Compilers and tools GO ?= $(shell which go) +# Build-time variables +ROOT_COMPILATION_UID ?= 65534 +ROOT_COMPILATION_GID ?= 65534 + build: mkdir -p build - cd src/bpm; $(GO) build -ldflags "-w" -o ../../build/bpm git.enumerated.dev/bubble-package-manager/bpm/src/bpm + cd src/bpm; $(GO) build -ldflags "-w -X 'git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib.rootCompilationUID=$(ROOT_COMPILATION_UID)' -X 'git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib.rootCompilationGID=$(ROOT_COMPILATION_GID)'" -o ../../build/bpm git.enumerated.dev/bubble-package-manager/bpm/src/bpm install: build/bpm config/ # Create directories diff --git a/src/bpmlib/compilation.go b/src/bpmlib/compilation.go index 1c7ea75..e0e0f99 100644 --- a/src/bpmlib/compilation.go +++ b/src/bpmlib/compilation.go @@ -9,8 +9,12 @@ import ( "path" "strconv" "strings" + "syscall" ) +var rootCompilationUID = "65534" +var rootCompilationGID = "65534" + func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks bool) (err error) { // Read BPM archive bpmpkg, err := ReadPackage(archiveFilename) @@ -35,6 +39,24 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo return err } + // Get UID and GID to use for compilation + var uid, gid int + if os.Getuid() == 0 { + _uid, err := strconv.ParseInt(rootCompilationUID, 10, 32) + if err != nil { + return fmt.Errorf("could not convert UID '%s' to int", rootCompilationUID) + } + _gid, err := strconv.ParseInt(rootCompilationGID, 10, 32) + if err != nil { + return fmt.Errorf("could not convert GID '%s' to int", rootCompilationGID) + } + uid = int(_uid) + gid = int(_gid) + } else { + uid = os.Getuid() + gid = os.Getgid() + } + tempDirectory := path.Join(homeDir, ".cache/bpm/compilation/", bpmpkg.PkgInfo.Name) // Ensure temporary directory does not exist @@ -51,8 +73,14 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo return err } + // Change temporary directory owner + err = os.Chown(tempDirectory, uid, gid) + if err != nil { + return err + } + // Extract source.sh file - err = extractTarballFile(archiveFilename, "source.sh", tempDirectory) + err = extractTarballFile(archiveFilename, "source.sh", tempDirectory, uid, gid) if err != nil { return err } @@ -60,14 +88,14 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo // Get package scripts and extract them packageScripts := getPackageScripts(archiveFilename) for _, script := range packageScripts { - err = extractTarballFile(archiveFilename, script, tempDirectory) + err = extractTarballFile(archiveFilename, script, tempDirectory, uid, gid) if err != nil { return err } } // Extract source files - err = extractTarballDirectory(archiveFilename, "source-files", tempDirectory) + err = extractTarballDirectory(archiveFilename, "source-files", tempDirectory, uid, gid) if err != nil { return err } @@ -78,6 +106,12 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo return err } + // Change source directory owner + err = os.Chown(path.Join(tempDirectory, "source"), uid, gid) + if err != nil { + return err + } + // Setup environment for commands env := os.Environ() env = append(env, "HOME="+tempDirectory) @@ -107,6 +141,8 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = env + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} err = cmd.Run() if err != nil { return err @@ -124,13 +160,15 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = env + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} err = cmd.Run() if err != nil { return err } } - // Remove 'output' directory if it already exists + // Remove output directory if it already exists if _, err := os.Stat(path.Join(tempDirectory, "output")); err == nil { err := os.RemoveAll(path.Join(tempDirectory, "output")) if err != nil { @@ -138,12 +176,18 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo } } - // Create new 'output' directory + // Create new output directory err = os.Mkdir(path.Join(tempDirectory, "output"), 0755) if err != nil { return err } + // Change output directory owner + err = os.Chown(path.Join(tempDirectory, "output"), uid, gid) + if err != nil { + return err + } + // Execute package function in source.sh script and generate package file list cmd = exec.Command("bash", "-c", "set -a\n"+ // Source and export functions and variables in source.sh script @@ -156,6 +200,8 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = env + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} err = cmd.Run() if err != nil { return err @@ -167,6 +213,8 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = env + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} err = cmd.Run() if err != nil { return fmt.Errorf("files.tar.gz archive could not be created: %s", err) @@ -198,13 +246,19 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo return err } + // Change pkg.info file owner + err = os.Chown(path.Join(tempDirectory, "pkg.info"), uid, gid) + if err != nil { + return err + } + // Get files to include in BPM archive bpmArchiveFiles := make([]string, 0) bpmArchiveFiles = append(bpmArchiveFiles, "pkg.info", "pkg.files", "files.tar.gz") // Base files bpmArchiveFiles = append(bpmArchiveFiles, packageScripts...) // Package scripts // Create final BPM archive - cmd = exec.Command("bash", "-c", "tar -cf "+outputFilename+" --owner=0 --group=0 -C \"$BPM_WORKDIR\" "+strings.Join(bpmArchiveFiles, " ")) + cmd = exec.Command("bash", "-c", "tar -cf final-archive.bpm --owner=0 --group=0 -C \"$BPM_WORKDIR\" "+strings.Join(bpmArchiveFiles, " ")) cmd.Dir = tempDirectory cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -213,11 +267,25 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo return err } cmd.Env = append(env, "CURRENT_DIR="+currentDir) + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} err = cmd.Run() if err != nil { return fmt.Errorf("BPM archive could not be created: %s", err) } + // Move final BPM archive + err = os.Rename(path.Join(tempDirectory, "final-archive.bpm"), outputFilename) + if err != nil { + return err + } + + // Set final BPM archive owner + err = os.Chown(outputFilename, os.Getuid(), os.Getgid()) + if err != nil { + return err + } + return nil } diff --git a/src/bpmlib/operations.go b/src/bpmlib/operations.go index f072759..1078be9 100644 --- a/src/bpmlib/operations.go +++ b/src/bpmlib/operations.go @@ -466,13 +466,43 @@ func (operation *BPMOperation) Execute(verbose, force bool) error { } } else if action.GetActionType() == "install" { value := action.(*InstallPackageAction) + fileToInstall := value.File bpmpkg := value.BpmPackage isReinstall := IsPackageInstalled(bpmpkg.PkgInfo.Name, operation.RootDir) var err error + + // Compile package if type is 'source' + if bpmpkg.PkgInfo.Type == "source" { + // Get path to compiled package directory and output filename + compiledDir := path.Join(operation.RootDir, "/var/lib/bpm/compiled/") + outputFilename := path.Join(compiledDir, fmt.Sprintf("%s-%s-%d.bpm", bpmpkg.PkgInfo.Name, bpmpkg.PkgInfo.Version, bpmpkg.PkgInfo.Revision)) + + // Create compiled package directory if not exists + if _, err := os.Stat(compiledDir); err != nil { + err := os.MkdirAll(compiledDir, 0755) + if err != nil { + return err + } + } + + // Compile source package + err = CompileSourcePackage(value.File, outputFilename, false) + if err != nil { + return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err) + } + + // Set values + fileToInstall = outputFilename + bpmpkg, err = ReadPackage(outputFilename) + if err != nil { + return fmt.Errorf("could not read package (%s): %s\n", fileToInstall, err) + } + } + if value.IsDependency { - err = installPackage(value.File, operation.RootDir, verbose, true) + err = installPackage(fileToInstall, operation.RootDir, verbose, true) } else { - err = installPackage(value.File, operation.RootDir, verbose, force) + err = installPackage(fileToInstall, operation.RootDir, verbose, force) } if err != nil { return errors.New(fmt.Sprintf("could not install package (%s): %s\n", bpmpkg.PkgInfo.Name, err)) diff --git a/src/bpmlib/packages.go b/src/bpmlib/packages.go index 85b6772..7f29197 100644 --- a/src/bpmlib/packages.go +++ b/src/bpmlib/packages.go @@ -607,6 +607,12 @@ func installPackage(filename, rootDir string, verbose, force bool) error { if err != nil { return err } + + // Ensure package type is 'binary' + if bpmpkg.PkgInfo.Type != "binary" { + return fmt.Errorf("can only extract binary packages") + } + packageInstalled := IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) // Check if package is installed and remove current files if packageInstalled { @@ -712,15 +718,10 @@ func installPackage(filename, rootDir string, verbose, force bool) error { fmt.Printf("Extracting files for package (%s)...\n", bpmpkg.PkgInfo.Name) } - if bpmpkg.PkgInfo.Type == "binary" { - err := extractPackage(bpmpkg, verbose, filename, rootDir) - if err != nil { - return err - } - } else if bpmpkg.PkgInfo.Type == "source" { - return errors.New("direct source package compilation in BPM has been temporarily removed and is being reworked on") - } else { - return errors.New("unknown package type: " + bpmpkg.PkgInfo.Type) + // Extract package files into rootDir + err = extractPackage(bpmpkg, verbose, filename, rootDir) + if err != nil { + return err } installedDir := path.Join(rootDir, "var/lib/bpm/installed/") diff --git a/src/bpmlib/tarball.go b/src/bpmlib/tarball.go index f60f96a..df9876d 100644 --- a/src/bpmlib/tarball.go +++ b/src/bpmlib/tarball.go @@ -72,7 +72,7 @@ func readTarballFile(tarballPath, fileToExtract string) (*tarballFileReader, err return nil, errors.New("could not file in tarball") } -func extractTarballFile(tarballPath, fileToExtract string, workingDirectory string) (err error) { +func extractTarballFile(tarballPath, fileToExtract string, workingDirectory string, uid, gid int) (err error) { file, err := os.Open(tarballPath) if err != nil { return err @@ -109,6 +109,12 @@ func extractTarballFile(tarballPath, fileToExtract string, workingDirectory stri if err != nil { return err } + if uid >= 0 && gid >= 0 { + err = file.Chown(uid, gid) + if err != nil { + return err + } + } // Copy data to file _, err = io.Copy(file, tr) @@ -126,7 +132,7 @@ func extractTarballFile(tarballPath, fileToExtract string, workingDirectory stri return nil } -func extractTarballDirectory(tarballPath, directoryToExtract, workingDirectory string) (err error) { +func extractTarballDirectory(tarballPath, directoryToExtract, workingDirectory string, uid, gid int) (err error) { file, err := os.Open(tarballPath) if err != nil { return err @@ -160,6 +166,14 @@ func extractTarballDirectory(tarballPath, directoryToExtract, workingDirectory s if err != nil { return err } + + // Set directory owner + if uid >= 0 && gid >= 0 { + err = os.Chown(outputPath, uid, gid) + if err != nil { + return err + } + } case tar.TypeReg: // Create file and set permissions file, err = os.Create(outputPath) @@ -170,6 +184,12 @@ func extractTarballDirectory(tarballPath, directoryToExtract, workingDirectory s if err != nil { return err } + if uid >= 0 && gid >= 0 { + err = file.Chown(uid, gid) + if err != nil { + return err + } + } // Copy data to file _, err = io.Copy(file, tr)