Compare commits

..

No commits in common. "7b0a8bf1d65c2888baaad51aa0901566557d1d23" and "a26737859cb23e1ec66af762e38c1cd9c759808d" have entirely different histories.

20 changed files with 867 additions and 659 deletions

5
.gitignore vendored
View File

@ -1,5 +0,0 @@
# IDE Directories
.idea
# Build directory
build

View File

@ -1,26 +1,38 @@
# Installation paths
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
SYSCONFDIR := $(PREFIX)/etc
SHELL = /bin/bash
# Compilers and tools
GO ?= $(shell which go)
ifeq ($(PREFIX),)
PREFIX := /usr/local
endif
ifeq ($(BINDIR),)
BINDIR := $(PREFIX)/bin
endif
ifeq ($(SYSCONFDIR),)
SYSCONFDIR := $(PREFIX)/etc
endif
ifeq ($(GO),)
GO := $(shell type -a -P go | head -n 1)
endif
build:
mkdir -p build
cd src/bpm; $(GO) build -ldflags "-w" -o ../../build/bpm git.enumerated.dev/bubble-package-manager/bpm/src/bpm
$(GO) build -ldflags "-w" -o build/bpm gitlab.com/bubble-package-manager/bpm
install: build/bpm config/
# Create directories
install -dm755 $(DESTDIR)$(BINDIR)
install -dm755 $(DESTDIR)$(SYSCONFDIR)
# Install files
install -Dm755 build/bpm $(DESTDIR)$(BINDIR)/bpm
install -Dm644 config/bpm.conf $(DESTDIR)$(SYSCONFDIR)/bpm.conf
mkdir -p $(DESTDIR)$(BINDIR)
mkdir -p $(DESTDIR)$(SYSCONFDIR)
cp build/bpm $(DESTDIR)$(BINDIR)/bpm
cp config/bpm.conf $(DESTDIR)$(SYSCONFDIR)/bpm.conf
uninstall:
rm $(DESTDIR)$(BINDIR)/bpm
rm $(DESTDIR)$(SYSCONFDIR)/bpm.conf
compress: build/bpm config/
mkdir -p bpm/$(BINDIR)
mkdir -p bpm/$(SYSCONFDIR)
cp build/bpm bpm/$(BINDIR)/bpm
cp config/bpm.conf bpm/$(SYSCONFDIR)/bpm.conf
tar --owner=root --group=root -czf bpm.tar.gz bpm
rm -r bpm
run: build/bpm
build/bpm
clean:
rm -r build/

View File

@ -5,22 +5,24 @@ BPM is a simple package manager for Linux systems
## Features
- Simple to use subcommands
- Can install binary packages (and source packages in the future)
- Can install binary and source packages
- Can be easily installed on practically any system
- No bloat
## Information
BPM is still in very early development. Do not install it without knowing what you are doing. I would only recommend using it in a Virtual Machine for testing
BPM is still in very early development. It should not be installed on any system you use seriously. I recommend trying this out in a VM or container. In addition to this, this is one of the first projects I have made using the go programming language so code quality may not be the best. This project was made to help me learn go and how linux systems work better. It is not meant to replace the big package managers in any way
## Build from source
- Download `go` from your package manager or from the go website
- Download `make` from your package manager
- Download `which` from your package manager
- Run the following command to compile the project. You may need to set the `GO` environment variable if your Go installation is not in your PATH
```sh
- Run the following command to compile the project
```
make
```
- Run the following command to install BPM into your system. You may also append a DESTDIR variable at the end of this line if you wish to install in a different location
```sh
- Run the following command to install stormfetch into your system. You may also append a DESTDIR variable at the end of this line if you wish to install in a different location
```
make install PREFIX=/usr SYSCONFDIR=/etc
```
@ -30,38 +32,24 @@ You are able to install bpm packages by typing the following:
```sh
bpm install /path/to/package.bpm
```
You can also use the package name directly if using repositories
```sh
bpm install package_name
```
The -y flag may be used as shown below to bypass the confirmation prompt
You may also use the -y flag as shown below to bypass the installation confirmation prompt
```sh
bpm install -y /path/to/package.bpm
```
Flags must strictly be typed before the first package path or name, otherwise they'll be read as package locations themselves
Flags must strictly be typed before the first package path otherwise they'll be read as package locations themselves
You can remove an installed package by typing the following
```sh
bpm remove package_name
```
To remove all unused dependencies try using the cleanup command
```sh
bpm cleanup
```
If using repositories, all packages can be updated using this simple command
```sh
bpm update
```
The -y flag applies here as well if you wish to skip the removal confirmation prompt
For information on the rest of the commands simply use the help command or pass in no arguments at all
```sh
```
bpm help
```
## Package Creation
Package creation is simplified using the bpm-utils package which contains helper scripts for creating and archiving packages
Learn more here: https://git.enumerated.dev/bubble-package-manager/bpm-utils
Package creation is simplified using the bpm-utils package which contains helper scripts for creating and archiving packages. \
Learn more here: https://gitlab.com/bubble-package-manager/bpm-utils

View File

@ -1,4 +1,7 @@
ignore_packages: []
compilation_env: []
silent_compilation: false
compilation_dir: "/var/tmp/"
binary_output_dir: "/var/lib/bpm/compiled/"
repositories:
- name: example-repository
source: https://my-repo.xyz/

9
go.mod Normal file
View File

@ -0,0 +1,9 @@
module gitlab.com/bubble-package-manager/bpm
go 1.22
require (
github.com/elliotchance/orderedmap/v2 v2.4.0 // indirect
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -1,6 +1,9 @@
github.com/elliotchance/orderedmap/v2 v2.4.0 h1:6tUmMwD9F998FNpwFxA5E6NQvSpk2PVw7RKsVq3+2Cw=
github.com/elliotchance/orderedmap/v2 v2.4.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -2,10 +2,9 @@ package main
import (
"bufio"
"errors"
"flag"
"fmt"
"git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib"
"gitlab.com/bubble-package-manager/bpm/utils"
"log"
"os"
"path/filepath"
@ -27,6 +26,9 @@ var subcommandArgs []string
var rootDir = "/"
var verbose = false
var yesAll = false
var buildSource = false
var skipCheck = false
var keepTempDir = false
var force = false
var pkgListNumbers = false
var pkgListNames = false
@ -40,10 +42,7 @@ var doCleanup = false
var showRepoInfo = false
func main() {
err := bpmlib.ReadConfig()
if err != nil {
log.Fatalf("Error: could not read BPM config: %s", err)
}
utils.ReadConfig()
resolveFlags()
resolveCommand()
}
@ -102,38 +101,31 @@ func resolveCommand() {
fmt.Println("No packages were given")
return
}
// Read local databases
err := bpmlib.ReadLocalDatabases()
if err != nil {
log.Fatalf("Error: could not read local databases: %s", err)
}
for n, pkg := range packages {
var info *bpmlib.PackageInfo
var info *utils.PackageInfo
isFile := false
if showRepoInfo {
var err error
var entry *bpmlib.RepositoryEntry
entry, _, err = bpmlib.GetRepositoryEntry(pkg)
var entry *utils.RepositoryEntry
entry, _, err = utils.GetRepositoryEntry(pkg)
if err != nil {
if entry = bpmlib.ResolveVirtualPackage(pkg); entry == nil {
if entry = utils.ResolveVirtualPackage(pkg); entry == nil {
log.Fatalf("Error: could not find package (%s) in any repository\n", pkg)
}
}
info = entry.Info
} else if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() {
bpmpkg, err := bpmlib.ReadPackage(pkg)
bpmpkg, err := utils.ReadPackage(pkg)
if err != nil {
log.Fatalf("Error: could not read package: %s\n", err)
}
info = bpmpkg.PkgInfo
isFile = true
} else {
if isVirtual, p := bpmlib.IsVirtualPackage(pkg, rootDir); isVirtual {
info = bpmlib.GetPackageInfo(p, rootDir)
if isVirtual, p := utils.IsVirtualPackage(pkg, rootDir); isVirtual {
info = utils.GetPackageInfo(p, rootDir)
} else {
info = bpmlib.GetPackageInfo(pkg, rootDir)
info = utils.GetPackageInfo(pkg, rootDir)
}
}
if info == nil {
@ -149,16 +141,10 @@ func resolveCommand() {
}
fmt.Println("File: " + abs)
}
fmt.Println(bpmlib.CreateReadableInfo(true, true, true, info, rootDir))
fmt.Println(utils.CreateReadableInfo(true, true, true, info, rootDir))
}
case list:
// Read local databases
err := bpmlib.ReadLocalDatabases()
if err != nil {
log.Fatalf("Error: could not read local databases: %s", err)
}
packages, err := bpmlib.GetInstalledPackages(rootDir)
packages, err := utils.GetInstalledPackages(rootDir)
if err != nil {
log.Fatalf("Error: could not get installed packages: %s", err.Error())
return
@ -175,7 +161,7 @@ func resolveCommand() {
return
}
for n, pkg := range packages {
info := bpmlib.GetPackageInfo(pkg, rootDir)
info := utils.GetPackageInfo(pkg, rootDir)
if info == nil {
fmt.Printf("Package (%s) could not be found\n", pkg)
continue
@ -183,7 +169,7 @@ func resolveCommand() {
if n != 0 {
fmt.Println()
}
fmt.Println(bpmlib.CreateReadableInfo(true, true, true, info, rootDir))
fmt.Println(utils.CreateReadableInfo(true, true, true, info, rootDir))
}
}
case search:
@ -191,17 +177,10 @@ func resolveCommand() {
if len(searchTerms) == 0 {
log.Fatalf("Error: no search terms given")
}
// Read local databases
err := bpmlib.ReadLocalDatabases()
if err != nil {
log.Fatalf("Error: could not read local databases: %s", err)
}
for i, term := range searchTerms {
nameResults := make([]*bpmlib.PackageInfo, 0)
descResults := make([]*bpmlib.PackageInfo, 0)
for _, repo := range bpmlib.BPMConfig.Repositories {
nameResults := make([]*utils.PackageInfo, 0)
descResults := make([]*utils.PackageInfo, 0)
for _, repo := range utils.BPMConfig.Repositories {
for _, entry := range repo.Entries {
if strings.Contains(entry.Info.Name, term) {
nameResults = append(nameResults, entry.Info)
@ -223,57 +202,106 @@ func resolveCommand() {
}
}
case install:
// Check for required permissions
if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
}
// Return if no packages are specified
if len(subcommandArgs) == 0 {
pkgs := subcommandArgs
if len(pkgs) == 0 {
fmt.Println("No packages or files were given to install")
return
}
// Check if installationReason argument is valid
ir := bpmlib.InstallationReasonUnknown
switch installationReason {
case "manual":
ir = bpmlib.InstallationReasonManual
case "dependency":
ir = bpmlib.InstallationReasonDependency
case "":
default:
ir := utils.Unknown
if installationReason == "manual" {
ir = utils.Manual
} else if installationReason == "dependency" {
ir = utils.Dependency
} else if installationReason != "" {
log.Fatalf("Error: %s is not a valid installation reason", installationReason)
}
// Get reinstall method
var reinstallMethod bpmlib.ReinstallMethod
if reinstallAll {
reinstallMethod = bpmlib.ReinstallMethodAll
} else if reinstall {
reinstallMethod = bpmlib.ReinstallMethodSpecified
} else {
reinstallMethod = bpmlib.ReinstallMethodNone
operation := utils.BPMOperation{
Actions: make([]utils.OperationAction, 0),
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RootDir: rootDir,
ForceInstallationReason: ir,
}
// Read local databases
err := bpmlib.ReadLocalDatabases()
// Search for packages
for _, pkg := range pkgs {
if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() {
bpmpkg, err := utils.ReadPackage(pkg)
if err != nil {
log.Fatalf("Error: could not read local databases: %s", err)
log.Fatalf("Error: could not read package: %s\n", err)
}
if !reinstall && utils.IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && utils.GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() {
continue
}
operation.AppendAction(&utils.InstallPackageAction{
File: pkg,
IsDependency: false,
BpmPackage: bpmpkg,
})
} else {
var entry *utils.RepositoryEntry
if e, _, err := utils.GetRepositoryEntry(pkg); err == nil {
entry = e
} else if isVirtual, p := utils.IsVirtualPackage(pkg, rootDir); isVirtual {
entry, _, err = utils.GetRepositoryEntry(p)
if err != nil {
log.Fatalf("Error: could not find package (%s) in any repository\n", p)
}
} else if e := utils.ResolveVirtualPackage(pkg); e != nil {
entry = e
} else {
log.Fatalf("Error: could not find package (%s) in any repository\n", pkg)
}
if !reinstall && utils.IsPackageInstalled(entry.Info.Name, rootDir) && utils.GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() {
continue
}
operation.AppendAction(&utils.FetchPackageAction{
IsDependency: false,
RepositoryEntry: entry,
})
}
}
// Create installation operation
operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallMethod, !noOptional, force, verbose, subcommandArgs...)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Fatalf("Error: %s", err)
} else if err != nil {
log.Fatalf("Error: could not setup operation: %s\n", err)
// Resolve dependencies
err := operation.ResolveDependencies(reinstallAll, !noOptional, verbose)
if err != nil {
log.Fatalf("Error: could not resolve dependencies: %s\n", err)
}
if len(operation.UnresolvedDepends) != 0 {
if !force {
log.Fatalf("Error: the following dependencies could not be found in any repositories: %s\n", strings.Join(operation.UnresolvedDepends, ", "))
} else {
log.Println("Warning: The following dependencies could not be found in any repositories: " + strings.Join(operation.UnresolvedDepends, ", "))
}
}
// Exit if operation contains no actions
if len(operation.Actions) == 0 {
fmt.Println("No action needs to be taken")
return
// Replace obsolete packages
operation.ReplaceObsoletePackages()
// Check for conflicts
conflicts, err := operation.CheckForConflicts()
if err != nil {
log.Fatalf("Error: could not complete package conflict check: %s\n", err)
}
if len(conflicts) > 0 {
if !force {
log.Println("Error: conflicting packages found")
} else {
log.Fatalf("Warning: conflicting packages found")
}
for pkg, conflict := range conflicts {
fmt.Printf("%s is in conflict with the following packages: %s\n", pkg, strings.Join(conflict, ", "))
}
if !force {
os.Exit(0)
}
}
// Show operation summary
@ -308,32 +336,80 @@ func resolveCommand() {
log.Fatalf("Error: could not run hooks: %s\n", err)
}
case update:
// Check for required permissions
if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
}
// Read local databases if no sync
if nosync {
err := bpmlib.ReadLocalDatabases()
// Sync repositories
if !nosync {
for _, repo := range utils.BPMConfig.Repositories {
fmt.Printf("Fetching package database for repository (%s)...\n", repo.Name)
err := repo.SyncLocalDatabase()
if err != nil {
log.Fatalf("Error: could not read local databases: %s", err)
log.Fatalf("Error: could not sync local database for repository (%s): %s\n", repo.Name, err)
}
}
fmt.Println("All package databases synced successfully!")
}
utils.ReadConfig()
// Get installed packages and check for updates
pkgs, err := utils.GetInstalledPackages(rootDir)
if err != nil {
log.Fatalf("Error: could not get installed packages: %s\n", err)
}
operation := utils.BPMOperation{
Actions: make([]utils.OperationAction, 0),
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RootDir: rootDir,
ForceInstallationReason: utils.Unknown,
}
// Search for packages
for _, pkg := range pkgs {
if slices.Contains(utils.BPMConfig.IgnorePackages, pkg) {
continue
}
var entry *utils.RepositoryEntry
// Check if installed package can be replaced and install that instead
if e := utils.FindReplacement(pkg); e != nil {
entry = e
} else if entry, _, err = utils.GetRepositoryEntry(pkg); err != nil {
continue
}
installedInfo := utils.GetPackageInfo(pkg, rootDir)
if installedInfo == nil {
log.Fatalf("Error: could not get package info for (%s)\n", pkg)
} else {
comparison := utils.ComparePackageVersions(*entry.Info, *installedInfo)
if comparison > 0 || reinstall {
operation.AppendAction(&utils.FetchPackageAction{
IsDependency: false,
RepositoryEntry: entry,
})
}
}
}
// Create update operation
operation, err := bpmlib.UpdatePackages(rootDir, !nosync, !noOptional, force, verbose)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Fatalf("Error: %s", err)
} else if err != nil {
log.Fatalf("Error: could not setup operation: %s\n", err)
// Check for new dependencies in updated packages
err = operation.ResolveDependencies(reinstallAll, !noOptional, verbose)
if err != nil {
log.Fatalf("Error: could not resolve dependencies: %s\n", err)
}
if len(operation.UnresolvedDepends) != 0 {
if !force {
log.Fatalf("Error: the following dependencies could not be found in any repositories: %s\n", strings.Join(operation.UnresolvedDepends, ", "))
} else {
log.Println("Warning: The following dependencies could not be found in any repositories: " + strings.Join(operation.UnresolvedDepends, ", "))
}
}
// Exit if operation contains no actions
if len(operation.Actions) == 0 {
fmt.Println("No action needs to be taken")
return
}
// Replace obsolete packages
operation.ReplaceObsoletePackages()
// Show operation summary
operation.ShowOperationSummary()
@ -362,12 +438,9 @@ func resolveCommand() {
log.Fatalf("Error: could not run hooks: %s\n", err)
}
case sync:
// Check for required permissions
if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
}
// Confirmation Prompt
if !yesAll {
fmt.Printf("Are you sure you wish to sync all databases? [y\\N] ")
reader := bufio.NewReader(os.Stdin)
@ -377,43 +450,54 @@ func resolveCommand() {
os.Exit(1)
}
}
// Sync databases
err := bpmlib.SyncDatabase(verbose)
for _, repo := range utils.BPMConfig.Repositories {
fmt.Printf("Fetching package database for repository (%s)...\n", repo.Name)
err := repo.SyncLocalDatabase()
if err != nil {
log.Fatalf("Error: could not sync local database: %s\n", err)
log.Fatalf("Error: could not sync local database for repository (%s): %s\n", repo.Name, err)
}
}
fmt.Println("All package databases synced successfully!")
case remove:
// Check for required permissions
if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
}
if len(subcommandArgs) == 0 {
packages := subcommandArgs
if len(packages) == 0 {
fmt.Println("No packages were given")
return
}
// Read local databases
err := bpmlib.ReadLocalDatabases()
operation := &utils.BPMOperation{
Actions: make([]utils.OperationAction, 0),
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RootDir: rootDir,
}
// Search for packages
for _, pkg := range packages {
bpmpkg := utils.GetPackage(pkg, rootDir)
if bpmpkg == nil {
continue
}
operation.AppendAction(&utils.RemovePackageAction{BpmPackage: bpmpkg})
}
// Skip needed packages if the --unused flag is on
if removeUnused {
err := operation.RemoveNeededPackages()
if err != nil {
log.Fatalf("Error: could not read local databases: %s", err)
log.Fatalf("Error: could not skip needed packages: %s\n", err)
}
}
// Create remove operation
operation, err := bpmlib.RemovePackages(rootDir, removeUnused, doCleanup, verbose, subcommandArgs...)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Fatalf("Error: %s", err)
} else if err != nil {
log.Fatalf("Error: could not setup operation: %s\n", err)
// Do package cleanup
if doCleanup {
err := operation.Cleanup(verbose)
if err != nil {
log.Fatalf("Error: could not perform cleanup for operation: %s\n", err)
}
// Exit if operation contains no actions
if len(operation.Actions) == 0 {
fmt.Println("No action needs to be taken")
return
}
// Show operation summary
@ -431,7 +515,7 @@ func resolveCommand() {
}
// Execute operation
err = operation.Execute(verbose, force)
err := operation.Execute(verbose, force)
if err != nil {
log.Fatalf("Error: could not complete operation: %s\n", err)
}
@ -443,29 +527,21 @@ func resolveCommand() {
log.Fatalf("Error: could not run hooks: %s\n", err)
}
case cleanup:
// Check for required permissions
if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
}
// Read local databases
err := bpmlib.ReadLocalDatabases()
operation := &utils.BPMOperation{
Actions: make([]utils.OperationAction, 0),
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RootDir: rootDir,
}
// Do package cleanup
err := operation.Cleanup(verbose)
if err != nil {
log.Fatalf("Error: could not read local databases: %s", err)
}
// Create cleanup operation
operation, err := bpmlib.CleanupPackages(rootDir, verbose)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Fatalf("Error: %s", err)
} else if err != nil {
log.Fatalf("Error: could not setup operation: %s\n", err)
}
// Exit if operation contains no actions
if len(operation.Actions) == 0 {
fmt.Println("No action needs to be taken")
return
log.Fatalf("Error: could not perform cleanup for operation: %s\n", err)
}
// Show operation summary
@ -509,7 +585,7 @@ func resolveCommand() {
if os.IsNotExist(err) {
log.Fatalf("Error: file (%s) does not exist!\n", absFile)
}
pkgs, err := bpmlib.GetInstalledPackages(rootDir)
pkgs, err := utils.GetInstalledPackages(rootDir)
if err != nil {
log.Fatalf("Error: could not get installed packages: %s\n", err.Error())
}
@ -528,7 +604,7 @@ func resolveCommand() {
var pkgList []string
for _, pkg := range pkgs {
if slices.ContainsFunc(bpmlib.GetPackageFiles(pkg, rootDir), func(entry *bpmlib.PackageFileEntry) bool {
if slices.ContainsFunc(utils.GetPackageFiles(pkg, rootDir), func(entry *utils.PackageFileEntry) bool {
return entry.Path == absFile
}) {
pkgList = append(pkgList, pkg)
@ -567,6 +643,9 @@ func printHelp() {
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt")
fmt.Println(" -f skips dependency, conflict and architecture checking")
fmt.Println(" -o=<path> set the binary package output directory (defaults to /var/lib/bpm/compiled)")
fmt.Println(" -c=<path> set the compilation directory (defaults to /var/tmp)")
fmt.Println(" -b creates a binary package from a source package after compilation and saves it in the binary package output directory")
fmt.Println(" -k keeps the compilation directory created by BPM after source package installation")
fmt.Println(" --reinstall Reinstalls packages even if they do not have a newer version available")
fmt.Println(" --reinstall-all Same as --reinstall but also reinstalls dependencies")
@ -587,8 +666,8 @@ func printHelp() {
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -y skips the confirmation prompt")
fmt.Println(" --unused removes only packages that aren't required as dependencies by other packages")
fmt.Println(" --cleanup performs a dependency cleanup")
fmt.Println(" -unused removes only packages that aren't required as dependencies by other packages")
fmt.Println(" -cleanup performs a dependency cleanup")
fmt.Println("-> bpm cleanup [-R, -v, -y] | remove all unused dependency packages")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -R=<path> lets you define the root path which will be used")
@ -615,6 +694,11 @@ func resolveFlags() {
installFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
installFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
installFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
installFlagSet.StringVar(&utils.BPMConfig.BinaryOutputDir, "o", utils.BPMConfig.BinaryOutputDir, "Set the binary output directory")
installFlagSet.StringVar(&utils.BPMConfig.CompilationDir, "c", utils.BPMConfig.CompilationDir, "Set the compilation directory")
installFlagSet.BoolVar(&buildSource, "b", false, "Build binary package from source package")
installFlagSet.BoolVar(&skipCheck, "s", false, "Skip check function during source compilation")
installFlagSet.BoolVar(&keepTempDir, "k", false, "Keep temporary directory after source compilation")
installFlagSet.BoolVar(&force, "f", false, "Force installation by skipping architecture and dependency resolution")
installFlagSet.BoolVar(&reinstall, "reinstall", false, "Reinstalls packages even if they do not have a newer version available")
installFlagSet.BoolVar(&reinstallAll, "reinstall-all", false, "Same as --reinstall but also reinstalls dependencies")
@ -627,6 +711,7 @@ func resolveFlags() {
updateFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
updateFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
updateFlagSet.BoolVar(&force, "f", false, "Force update by skipping architecture and dependency resolution")
updateFlagSet.BoolVar(&reinstall, "reinstall", false, "Fetches and reinstalls all packages even if they do not have a newer version available")
updateFlagSet.BoolVar(&nosync, "no-sync", false, "Skips package database syncing")
updateFlagSet.Usage = printHelp
// Sync flags

View File

@ -1,14 +0,0 @@
module git.enumerated.dev/bubble-package-manager/bpm/src/bpm
go 1.23
toolchain go1.23.7
require git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib v0.5.0
replace git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib => ../bpmlib
require (
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -1,6 +0,0 @@
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,38 +0,0 @@
package bpmlib
import (
"gopkg.in/yaml.v3"
"os"
)
type BPMConfigStruct struct {
IgnorePackages []string `yaml:"ignore_packages"`
Repositories []*Repository `yaml:"repositories"`
}
var BPMConfig BPMConfigStruct
func ReadConfig() (err error) {
if _, err = os.Stat("/etc/bpm.conf"); os.IsNotExist(err) {
return err
}
bytes, err := os.ReadFile("/etc/bpm.conf")
if err != nil {
return err
}
BPMConfig = BPMConfigStruct{}
err = yaml.Unmarshal(bytes, &BPMConfig)
if err != nil {
return err
}
for i := len(BPMConfig.Repositories) - 1; i >= 0; i-- {
if BPMConfig.Repositories[i].Disabled != nil && *BPMConfig.Repositories[i].Disabled {
BPMConfig.Repositories = append(BPMConfig.Repositories[:i], BPMConfig.Repositories[i+1:]...)
}
}
return nil
}

View File

@ -1,31 +0,0 @@
package bpmlib
import (
"fmt"
"strings"
)
type PackageNotFoundErr struct {
packages []string
}
func (e PackageNotFoundErr) Error() string {
return "The following packages were not found in any repositories: " + strings.Join(e.packages, ", ")
}
type DependencyNotFoundErr struct {
dependencies []string
}
func (e DependencyNotFoundErr) Error() string {
return "The following dependencies were not found in any repositories: " + strings.Join(e.dependencies, ", ")
}
type PackageConflictErr struct {
pkg string
conflicts []string
}
func (e PackageConflictErr) Error() string {
return fmt.Sprintf("Package (%s) is in conflict with the following packages: %s", e.pkg, strings.Join(e.conflicts, ", "))
}

View File

@ -1,260 +0,0 @@
package bpmlib
import (
"errors"
"fmt"
"log"
"os"
"slices"
)
type ReinstallMethod uint8
const (
ReinstallMethodNone ReinstallMethod = iota
ReinstallMethodSpecified ReinstallMethod = iota
ReinstallMethodAll ReinstallMethod = iota
)
// InstallPackages installs the specified packages into the given root directory by fetching them from repositories or directly from local bpm archives
func InstallPackages(rootDir string, installationReason InstallationReason, reinstallMethod ReinstallMethod, installOptionalDependencies, forceInstallation, verbose bool, packages ...string) (operation *BPMOperation, err error) {
// Setup operation struct
operation = &BPMOperation{
Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RootDir: rootDir,
ForceInstallationReason: installationReason,
}
// Resolve packages
pkgsNotFound := make([]string, 0)
for _, pkg := range packages {
if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() {
bpmpkg, err := ReadPackage(pkg)
if err != nil {
return nil, fmt.Errorf("could not read package: %s", err)
}
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() {
continue
}
operation.AppendAction(&InstallPackageAction{
File: pkg,
IsDependency: false,
BpmPackage: bpmpkg,
})
} else {
var entry *RepositoryEntry
if e, _, err := GetRepositoryEntry(pkg); err == nil {
entry = e
} else if isVirtual, p := IsVirtualPackage(pkg, rootDir); isVirtual {
entry, _, err = GetRepositoryEntry(p)
if err != nil {
pkgsNotFound = append(pkgsNotFound, pkg)
continue
}
} else if e := ResolveVirtualPackage(pkg); e != nil {
entry = e
} else {
pkgsNotFound = append(pkgsNotFound, pkg)
continue
}
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() {
continue
}
operation.AppendAction(&FetchPackageAction{
IsDependency: false,
RepositoryEntry: entry,
})
}
}
// Return error if not all packages are found
if len(pkgsNotFound) != 0 {
return nil, PackageNotFoundErr{pkgsNotFound}
}
// Resolve dependencies
err = operation.ResolveDependencies(reinstallMethod == ReinstallMethodAll, installOptionalDependencies, verbose)
if err != nil {
return nil, fmt.Errorf("could not resolve dependencies: %s", err)
}
if len(operation.UnresolvedDepends) != 0 {
if !forceInstallation {
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
} else if verbose {
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends})
}
}
// Replace obsolete packages
operation.ReplaceObsoletePackages()
// Check for conflicts
conflicts, err := operation.CheckForConflicts()
if err != nil {
return nil, fmt.Errorf("could not complete package conflict check: %s", err)
}
if len(conflicts) > 0 {
err = nil
for pkg, conflict := range conflicts {
err = errors.Join(err, PackageConflictErr{pkg, conflict})
}
if !forceInstallation {
return nil, err
} else {
log.Printf("Warning: %s", err)
}
}
return operation, nil
}
// RemovePackages removes the specified packages from the given root directory
func RemovePackages(rootDir string, removeUnusedPackagesOnly, cleanupDependencies, verbose bool, packages ...string) (operation *BPMOperation, err error) {
operation = &BPMOperation{
Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RootDir: rootDir,
}
// Search for packages
for _, pkg := range packages {
bpmpkg := GetPackage(pkg, rootDir)
if bpmpkg == nil {
continue
}
operation.AppendAction(&RemovePackageAction{BpmPackage: bpmpkg})
}
// Do not remove packages which other packages depend on
if removeUnusedPackagesOnly {
err := operation.RemoveNeededPackages()
if err != nil {
return nil, fmt.Errorf("could not skip needed packages: %s", err)
}
}
// Do package cleanup
if cleanupDependencies {
err := operation.Cleanup(verbose)
if err != nil {
return nil, fmt.Errorf("could not perform cleanup for operation: %s", err)
}
}
return operation, nil
}
// CleanupPackages finds packages installed as dependencies which are no longer required by the rest of the system in the given root directory
func CleanupPackages(rootDir string, verbose bool) (operation *BPMOperation, err error) {
operation = &BPMOperation{
Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RootDir: rootDir,
}
// Do package cleanup
err = operation.Cleanup(verbose)
if err != nil {
return nil, fmt.Errorf("could not perform cleanup for operation: %s", err)
}
return operation, nil
}
// UpdatePackages fetches the newest versions of all installed packages from
func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) {
// Sync repositories
if syncDatabase {
err := SyncDatabase(verbose)
if err != nil {
return nil, fmt.Errorf("could not sync local database: %s", err)
}
if verbose {
fmt.Println("All package databases synced successfully!")
}
}
// Reload config and local databases
err = ReadConfig()
if err != nil {
return nil, fmt.Errorf("could not read BPM config: %s", err)
}
// Get installed packages and check for updates
pkgs, err := GetInstalledPackages(rootDir)
if err != nil {
return nil, fmt.Errorf("could not get installed packages: %s", err)
}
operation = &BPMOperation{
Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RootDir: rootDir,
ForceInstallationReason: InstallationReasonUnknown,
}
// Search for packages
for _, pkg := range pkgs {
if slices.Contains(BPMConfig.IgnorePackages, pkg) {
continue
}
var entry *RepositoryEntry
// Check if installed package can be replaced and install that instead
if e := FindReplacement(pkg); e != nil {
entry = e
} else if entry, _, err = GetRepositoryEntry(pkg); err != nil {
continue
}
installedInfo := GetPackageInfo(pkg, rootDir)
if installedInfo == nil {
return nil, fmt.Errorf("could not get package info for package (%s)", pkg)
} else {
comparison := ComparePackageVersions(*entry.Info, *installedInfo)
if comparison > 0 {
operation.AppendAction(&FetchPackageAction{
IsDependency: false,
RepositoryEntry: entry,
})
}
}
}
// Check for new dependencies in updated packages
err = operation.ResolveDependencies(false, installOptionalDependencies, verbose)
if err != nil {
return nil, fmt.Errorf("could not resolve dependencies: %s", err)
}
if len(operation.UnresolvedDepends) != 0 {
if !forceInstallation {
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
} else if verbose {
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends})
}
}
// Replace obsolete packages
operation.ReplaceObsoletePackages()
return operation, nil
}
// SyncDatabase syncs all databases declared in /etc/bpm.conf
func SyncDatabase(verbose bool) (err error) {
for _, repo := range BPMConfig.Repositories {
if verbose {
fmt.Printf("Fetching package database for repository (%s)...\n", repo.Name)
}
err := repo.SyncLocalDatabase()
if err != nil {
return err
}
}
return nil
}

View File

@ -1,8 +0,0 @@
module git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib
go 1.23
require (
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f
gopkg.in/yaml.v3 v3.0.1
)

51
utils/config.go Normal file
View File

@ -0,0 +1,51 @@
package utils
import (
"gopkg.in/yaml.v3"
"log"
"os"
)
type BPMConfigStruct struct {
CompilationEnv []string `yaml:"compilation_env"`
SilentCompilation bool `yaml:"silent_compilation"`
BinaryOutputDir string `yaml:"binary_output_dir"`
CompilationDir string `yaml:"compilation_dir"`
IgnorePackages []string `yaml:"ignore_packages"`
Repositories []*Repository `yaml:"repositories"`
}
var BPMConfig BPMConfigStruct
func ReadConfig() {
if _, err := os.Stat("/etc/bpm.conf"); os.IsNotExist(err) {
log.Fatal(err)
}
bytes, err := os.ReadFile("/etc/bpm.conf")
if err != nil {
log.Fatal(err)
}
BPMConfig = BPMConfigStruct{
CompilationEnv: make([]string, 0),
SilentCompilation: false,
BinaryOutputDir: "/var/lib/bpm/compiled/",
CompilationDir: "/var/tmp/",
}
err = yaml.Unmarshal(bytes, &BPMConfig)
if err != nil {
log.Fatal(err)
}
for i := len(BPMConfig.Repositories) - 1; i >= 0; i-- {
if BPMConfig.Repositories[i].Disabled != nil && *BPMConfig.Repositories[i].Disabled {
BPMConfig.Repositories = append(BPMConfig.Repositories[:i], BPMConfig.Repositories[i+1:]...)
}
}
for _, repo := range BPMConfig.Repositories {
repo.Entries = make(map[string]*RepositoryEntry)
repo.VirtualPackages = make(map[string][]string)
err := repo.ReadLocalDatabase()
if err != nil {
log.Fatal(err)
}
}
}

View File

@ -1,4 +1,4 @@
package bpmlib
package utils
import (
"archive/tar"
@ -7,12 +7,12 @@ import (
"os"
)
type tarballFileReader struct {
type TarballFileReader struct {
tarReader *tar.Reader
file *os.File
}
func readTarballContent(tarballPath, fileToExtract string) (*tarballFileReader, error) {
func ReadTarballContent(tarballPath, fileToExtract string) (*TarballFileReader, error) {
file, err := os.Open(tarballPath)
if err != nil {
return nil, err
@ -32,7 +32,7 @@ func readTarballContent(tarballPath, fileToExtract string) (*tarballFileReader,
return nil, errors.New("file to extract must be a regular file")
}
return &tarballFileReader{
return &TarballFileReader{
tarReader: tr,
file: file,
}, nil

View File

@ -1,4 +1,4 @@
package bpmlib
package utils
import (
"fmt"
@ -55,7 +55,7 @@ func stringSliceRemove(s []string, r string) []string {
return s
}
func unsignedBytesToHumanReadable(b uint64) string {
func UnsignedBytesToHumanReadable(b uint64) string {
bf := float64(b)
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
if math.Abs(bf) < 1024.0 {
@ -66,7 +66,7 @@ func unsignedBytesToHumanReadable(b uint64) string {
return fmt.Sprintf("%.1fYiB", bf)
}
func bytesToHumanReadable(b int64) string {
func BytesToHumanReadable(b int64) string {
bf := float64(b)
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
if math.Abs(bf) < 1024.0 {

View File

@ -1,4 +1,4 @@
package bpmlib
package utils
import (
"errors"
@ -23,8 +23,8 @@ type BPMHook struct {
Run string `yaml:"run"`
}
// createHook returns a BPMHook instance based on the content of the given string
func createHook(sourcePath string) (*BPMHook, error) {
// CreateHook returns a BPMHook instance based on the content of the given string
func CreateHook(sourcePath string) (*BPMHook, error) {
// Read hook from source path
bytes, err := os.ReadFile(sourcePath)
if err != nil {

View File

@ -1,4 +1,4 @@
package bpmlib
package utils
import (
"errors"
@ -195,7 +195,7 @@ func (operation *BPMOperation) Cleanup(verbose bool) error {
// Get all installed packages
installedPackageNames, err := GetInstalledPackages(operation.RootDir)
if err != nil {
return fmt.Errorf("could not get installed packages: %s", err)
log.Fatalf("Error: could not get installed packages: %s\n", err)
}
installedPackages := make([]*PackageInfo, len(installedPackageNames))
for i, value := range installedPackageNames {
@ -217,7 +217,7 @@ func (operation *BPMOperation) Cleanup(verbose bool) error {
// Get manually installed packages, resolve all their dependencies and add them to the keepPackages slice
keepPackages := make([]string, 0)
for _, pkg := range slices.Clone(installedPackages) {
if GetInstallationReason(pkg.Name, operation.RootDir) != InstallationReasonManual {
if GetInstallationReason(pkg.Name, operation.RootDir) != Manual {
continue
}
@ -329,8 +329,8 @@ func (operation *BPMOperation) CheckForConflicts() (map[string][]string, error)
func (operation *BPMOperation) ShowOperationSummary() {
if len(operation.Actions) == 0 {
fmt.Println("No action needs to be taken")
return
fmt.Println("All packages are up to date!")
os.Exit(0)
}
for _, value := range operation.Actions {
@ -348,6 +348,9 @@ func (operation *BPMOperation) ShowOperationSummary() {
installedInfo := GetPackageInfo(pkgInfo.Name, operation.RootDir)
sourceInfo := ""
if pkgInfo.Type == "source" {
if operation.RootDir != "/" {
log.Fatalf("cannot compile and install source packages to a different root directory")
}
sourceInfo = "(From Source)"
}
@ -369,21 +372,16 @@ func (operation *BPMOperation) ShowOperationSummary() {
fmt.Println("Warning: Operating in " + operation.RootDir)
}
if operation.GetTotalDownloadSize() > 0 {
fmt.Printf("%s will be downloaded to complete this operation\n", unsignedBytesToHumanReadable(operation.GetTotalDownloadSize()))
fmt.Printf("%s will be downloaded to complete this operation\n", UnsignedBytesToHumanReadable(operation.GetTotalDownloadSize()))
}
if operation.GetFinalActionSize(operation.RootDir) > 0 {
fmt.Printf("A total of %s will be installed after the operation finishes\n", bytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)))
fmt.Printf("A total of %s will be installed after the operation finishes\n", BytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)))
} else if operation.GetFinalActionSize(operation.RootDir) < 0 {
fmt.Printf("A total of %s will be freed after the operation finishes\n", strings.TrimPrefix(bytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)), "-"))
fmt.Printf("A total of %s will be freed after the operation finishes\n", strings.TrimPrefix(BytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)), "-"))
}
}
func (operation *BPMOperation) RunHooks(verbose bool) error {
// Return if hooks directory does not exist
if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() {
return nil
}
// Get directory entries in hooks directory
dirEntries, err := os.ReadDir(path.Join(operation.RootDir, "var/lib/bpm/hooks"))
if err != nil {
@ -393,7 +391,7 @@ func (operation *BPMOperation) RunHooks(verbose bool) error {
// Find all hooks, validate and execute them
for _, entry := range dirEntries {
if entry.Type().IsRegular() && strings.HasSuffix(entry.Name(), ".bpmhook") {
hook, err := createHook(path.Join(operation.RootDir, "var/lib/bpm/hooks", entry.Name()))
hook, err := CreateHook(path.Join(operation.RootDir, "var/lib/bpm/hooks", entry.Name()))
if err != nil {
log.Printf("Error while reading hook (%s): %s", entry.Name(), err)
}
@ -460,7 +458,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) error {
for _, action := range operation.Actions {
if action.GetActionType() == "remove" {
pkgInfo := action.(*RemovePackageAction).BpmPackage.PkgInfo
err := removePackage(pkgInfo.Name, verbose, operation.RootDir)
err := RemovePackage(pkgInfo.Name, verbose, operation.RootDir)
if err != nil {
return errors.New(fmt.Sprintf("could not remove package (%s): %s\n", pkgInfo.Name, err))
}
@ -470,20 +468,20 @@ func (operation *BPMOperation) Execute(verbose, force bool) error {
isReinstall := IsPackageInstalled(bpmpkg.PkgInfo.Name, operation.RootDir)
var err error
if value.IsDependency {
err = installPackage(value.File, operation.RootDir, verbose, true)
err = InstallPackage(value.File, operation.RootDir, verbose, true, false, false, false)
} else {
err = installPackage(value.File, operation.RootDir, verbose, force)
err = InstallPackage(value.File, operation.RootDir, verbose, force, false, false, false)
}
if err != nil {
return errors.New(fmt.Sprintf("could not install package (%s): %s\n", bpmpkg.PkgInfo.Name, err))
}
if operation.ForceInstallationReason != InstallationReasonUnknown && !value.IsDependency {
if operation.ForceInstallationReason != Unknown && !value.IsDependency {
err := SetInstallationReason(bpmpkg.PkgInfo.Name, operation.ForceInstallationReason, operation.RootDir)
if err != nil {
return errors.New(fmt.Sprintf("could not set installation reason for package (%s): %s\n", value.BpmPackage.PkgInfo.Name, err))
}
} else if value.IsDependency && !isReinstall {
err := SetInstallationReason(bpmpkg.PkgInfo.Name, InstallationReasonDependency, operation.RootDir)
err := SetInstallationReason(bpmpkg.PkgInfo.Name, Dependency, operation.RootDir)
if err != nil {
return errors.New(fmt.Sprintf("could not set installation reason for package (%s): %s\n", value.BpmPackage.PkgInfo.Name, err))
}

View File

@ -1,4 +1,4 @@
package bpmlib
package utils
import (
"archive/tar"
@ -8,13 +8,16 @@ import (
version "github.com/knqyf263/go-rpm-version"
"gopkg.in/yaml.v3"
"io"
"io/fs"
"os"
"os/exec"
"path"
"path/filepath"
"slices"
"sort"
"strconv"
"strings"
"syscall"
)
type BPMPackage struct {
@ -71,9 +74,9 @@ func (pkgInfo *PackageInfo) GetFullVersion() string {
type InstallationReason string
const (
InstallationReasonManual InstallationReason = "manual"
InstallationReasonDependency InstallationReason = "dependency"
InstallationReasonUnknown InstallationReason = "unknown"
Manual InstallationReason = "manual"
Dependency InstallationReason = "dependency"
Unknown InstallationReason = "unknown"
)
func ComparePackageVersions(info1, info2 PackageInfo) int {
@ -87,19 +90,19 @@ func GetInstallationReason(pkg, rootDir string) InstallationReason {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
if stat, err := os.Stat(path.Join(pkgDir, "installation_reason")); err != nil || stat.IsDir() {
return InstallationReasonManual
return Manual
}
b, err := os.ReadFile(path.Join(pkgDir, "installation_reason"))
if err != nil {
return InstallationReasonUnknown
return Unknown
}
reason := strings.TrimSpace(string(b))
if reason == "manual" {
return InstallationReasonManual
return Manual
} else if reason == "dependency" {
return InstallationReasonDependency
return Dependency
}
return InstallationReasonUnknown
return Unknown
}
func SetInstallationReason(pkg string, reason InstallationReason, rootDir string) error {
@ -277,15 +280,15 @@ func ReadPackageScripts(filename string) (map[string]string, error) {
return ret, nil
}
type packageOperation uint8
type Operation uint8
const (
packageOperationInstall packageOperation = 0
packageOperationUpdate = 1
packageOperationRemove = 2
Install Operation = 0
Update = 1
Remove = 2
)
func executePackageScripts(filename, rootDir string, operation packageOperation, postOperation bool) error {
func ExecutePackageScripts(filename, rootDir string, operation Operation, postOperation bool) error {
pkgInfo, err := ReadPackage(filename)
if err != nil {
return err
@ -306,17 +309,22 @@ func executePackageScripts(filename, rootDir string, operation packageOperation,
}
cmd := exec.Command("/bin/bash", temp.Name())
if !BPMConfig.SilentCompilation {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
cmd.Dir = rootDir
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_ROOT=%s", rootDir))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_NAME=%s", pkgInfo.PkgInfo.Name))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_DESC=%s", pkgInfo.PkgInfo.Description))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_VERSION=%s", pkgInfo.PkgInfo.Version))
if operation != packageOperationInstall {
if operation != Install {
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_OLD_VERSION=%s", GetPackageInfo(pkgInfo.PkgInfo.Name, rootDir).Version))
}
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_REVISION=%d", pkgInfo.PkgInfo.Revision))
if operation != packageOperationInstall {
if operation != Install {
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_OLD_REVISION=%d", GetPackageInfo(pkgInfo.PkgInfo.Name, rootDir).Revision))
}
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_URL=%s", pkgInfo.PkgInfo.Url))
@ -341,7 +349,7 @@ func executePackageScripts(filename, rootDir string, operation packageOperation,
return nil
}
if operation == packageOperationInstall {
if operation == Install {
if val, ok := scripts["pre_install.sh"]; !postOperation && ok {
err := run("pre_install.sh", val)
if err != nil {
@ -354,7 +362,7 @@ func executePackageScripts(filename, rootDir string, operation packageOperation,
return err
}
}
} else if operation == packageOperationUpdate {
} else if operation == Update {
if val, ok := scripts["pre_update.sh"]; !postOperation && ok {
err := run("pre_update.sh", val)
if err != nil {
@ -367,7 +375,7 @@ func executePackageScripts(filename, rootDir string, operation packageOperation,
return err
}
}
} else if operation == packageOperationRemove {
} else if operation == Remove {
if val, ok := scripts["pre_remove.sh"]; !postOperation && ok {
err := run("pre_remove.sh", val)
if err != nil {
@ -425,6 +433,14 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
return &pkgInfo, nil
}
func CreateInfoFile(pkgInfo *PackageInfo) string {
b, err := yaml.Marshal(&pkgInfo)
if err != nil {
return ""
}
return string(b)
}
func CreateReadableInfo(showArchitecture, showType, showPackageRelations bool, pkgInfo *PackageInfo, rootDir string) string {
ret := make([]string, 0)
appendArray := func(label string, array []string) {
@ -446,9 +462,7 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations bool, p
}
if showPackageRelations {
appendArray("Dependencies", pkgInfo.Depends)
if pkgInfo.Type == "source" {
appendArray("Make Dependencies", pkgInfo.MakeDepends)
}
appendArray("Optional dependencies", pkgInfo.OptionalDepends)
dependants, err := pkgInfo.GetDependants(rootDir)
if err == nil {
@ -464,12 +478,12 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations bool, p
func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string) error {
if !IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
err := executePackageScripts(filename, rootDir, packageOperationInstall, false)
err := ExecutePackageScripts(filename, rootDir, Install, false)
if err != nil {
return err
}
} else {
err := executePackageScripts(filename, rootDir, packageOperationUpdate, false)
err := ExecutePackageScripts(filename, rootDir, Update, false)
if err != nil {
return err
}
@ -480,7 +494,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
return err
}
tarballFile, err := readTarballContent(filename, "files.tar.gz")
tarballFile, err := ReadTarballContent(filename, "files.tar.gz")
if err != nil {
return err
}
@ -597,7 +611,447 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
return nil
}
func installPackage(filename, rootDir string, verbose, force bool) error {
func isSplitPackage(filename string) bool {
pkgInfo, err := ReadPackage(filename)
if err != nil {
return false
}
if pkgInfo.PkgInfo.Type != "source" {
return false
}
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("test $(tar -tf %s | grep '^pkg.info' | wc -l) -eq 1", filename))
if err := cmd.Run(); err == nil {
return false
}
return true
}
func compilePackage(bpmpkg *BPMPackage, filename, rootDir string, verbose, binaryPkgFromSrc, skipCheck, keepTempDir bool) (error, []string) {
var files []string
if !IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
err := ExecutePackageScripts(filename, rootDir, Install, false)
if err != nil {
return err, nil
}
} else {
err := ExecutePackageScripts(filename, rootDir, Update, false)
if err != nil {
return err, nil
}
}
//seenHardlinks := make(map[string]string)
file, err := os.Open(filename)
if err != nil {
return err, nil
}
tr := tar.NewReader(file)
temp := path.Join(BPMConfig.CompilationDir, "bpm_source-"+bpmpkg.PkgInfo.Name)
err = os.RemoveAll(temp)
if err != nil {
return err, nil
}
if verbose {
fmt.Println("Creating temp directory at: " + temp)
}
err = os.Mkdir(temp, 0755)
if err != nil {
return err, nil
}
err = os.Chown(temp, 65534, 65534)
if err != nil {
return err, nil
}
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err, nil
}
if strings.HasPrefix(header.Name, "source-files/") && header.Name != "source-files/" {
extractFilename := path.Join(temp, strings.TrimPrefix(header.Name, "source-files/"))
switch header.Typeflag {
case tar.TypeDir:
if err := os.Mkdir(extractFilename, 0755); err != nil {
if !os.IsExist(err) {
return err, nil
}
} else {
if verbose {
fmt.Println("Creating Directory: " + extractFilename)
}
err = os.Chown(extractFilename, 65534, 65534)
if err != nil {
return err, nil
}
}
case tar.TypeReg:
err := os.Remove(extractFilename)
if err != nil && !os.IsNotExist(err) {
return err, nil
}
outFile, err := os.Create(extractFilename)
if verbose {
fmt.Println("Creating File: " + extractFilename)
}
if err != nil {
return err, nil
}
err = os.Chown(extractFilename, 65534, 65534)
if err != nil {
return err, nil
}
if _, err := io.Copy(outFile, tr); err != nil {
return err, nil
}
if err := os.Chmod(extractFilename, header.FileInfo().Mode()); err != nil {
return err, nil
}
err = outFile.Close()
if err != nil {
return err, nil
}
case tar.TypeSymlink:
if verbose {
fmt.Println("Skipping symlink (Bundling symlinks in source packages is not supported)")
}
case tar.TypeLink:
if verbose {
fmt.Println("Skipping hard link (Bundling hard links in source packages is not supported)")
}
default:
return errors.New("unknown type (" + strconv.Itoa(int(header.Typeflag)) + ") in " + extractFilename), nil
}
}
if header.Name == "source.sh" {
bs, err := io.ReadAll(tr)
if err != nil {
return err, nil
}
err = os.WriteFile(path.Join(temp, "source.sh"), bs, 0644)
if err != nil {
return err, nil
}
err = os.Chown(path.Join(temp, "source.sh"), 65534, 65534)
if err != nil {
return err, nil
}
}
}
if _, err := os.Stat(path.Join(temp, "source.sh")); os.IsNotExist(err) {
return errors.New("source.sh file could not be found in the temporary build directory"), nil
}
fmt.Println("Running source.sh file...")
if !IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
err = ExecutePackageScripts(filename, rootDir, Install, false)
if err != nil {
return err, nil
}
} else {
err = ExecutePackageScripts(filename, rootDir, Update, false)
if err != nil {
return err, nil
}
}
bs, err := os.ReadFile(path.Join(temp, "source.sh"))
if err != nil {
return err, nil
}
if !strings.Contains(string(bs), "package()") {
fmt.Print("This package does not seem to have the required 'package' function\nThe source.sh file may have been created for an older BPM version\nPlease update the source.sh file")
return errors.New("invalid source.sh format"), nil
}
runScript := `
cd "$BPM_WORKDIR"
set -a
source "source.sh"
set +a
if [[ $(type -t prepare) == function ]]; then
echo "Running prepare() function..."
bash -e -c prepare
if [ $? -ne 0 ]; then
echo "Failed to run prepare() function in source.sh"
exit 1
fi
fi
cd "$BPM_SOURCE"
if [[ $(type -t build) == function ]]; then
echo "Running build() function..."
bash -e -c build
if [ $? -ne 0 ]; then
echo "Failed to run build() function in source.sh"
exit 1
fi
fi
cd "$BPM_SOURCE"
if [[ $(type -t check) == function ]] && [ -z "$SKIPCHECK" ]; then
echo "Running check() function..."
bash -e -c check
if [ $? -ne 0 ]; then
echo "Failed to run check() function in source.sh"
exit 1
fi
fi
cd "$BPM_SOURCE"
if ! [[ $(type -t package) == function ]]; then
echo "Failed to locate package() function in source.sh"
exit 1
fi
echo "Running package() function..."
touch "$BPM_WORKDIR"/fakeroot_file
fakeroot -s "$BPM_WORKDIR"/fakeroot_file bash -e -c package
bash -e -c package
if [ $? -ne 0 ]; then
echo "Failed to run package() function in source.sh"
fi
`
err = os.WriteFile(path.Join(temp, "run.sh"), []byte(runScript), 0644)
if err != nil {
return err, nil
}
err = os.Chown(path.Join(temp, "run.sh"), 65534, 65534)
if err != nil {
return err, nil
}
cmd := exec.Command("/bin/bash", "-e", "run.sh")
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: 65534, Gid: 65534}
cmd.Dir = temp
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "USER=nobody")
cmd.Env = append(cmd.Env, "HOME="+temp)
err = os.Mkdir(path.Join(temp, "source"), 0755)
if err != nil {
return err, nil
}
err = os.Chown(path.Join(temp, "source"), 65534, 65534)
if err != nil {
return err, nil
}
err = os.Mkdir(path.Join(temp, "output"), 0755)
if err != nil {
return err, nil
}
err = os.Chown(path.Join(temp, "output"), 65534, 65534)
if err != nil {
return err, nil
}
cmd.Env = append(cmd.Env, "BPM_WORKDIR="+temp)
cmd.Env = append(cmd.Env, "BPM_SOURCE="+path.Join(temp, "source"))
cmd.Env = append(cmd.Env, "BPM_OUTPUT="+path.Join(temp, "output"))
cmd.Env = append(cmd.Env, "SKIPCHECK="+strconv.FormatBool(skipCheck))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_ROOT=%s", rootDir))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_NAME=%s", bpmpkg.PkgInfo.Name))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_DESC=%s", bpmpkg.PkgInfo.Description))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_VERSION=%s", bpmpkg.PkgInfo.Version))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_REVISION=%d", bpmpkg.PkgInfo.Revision))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_URL=%s", bpmpkg.PkgInfo.Url))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_ARCH=%s", bpmpkg.PkgInfo.Arch))
depends := make([]string, len(bpmpkg.PkgInfo.Depends))
copy(depends, bpmpkg.PkgInfo.Depends)
for i := 0; i < len(depends); i++ {
depends[i] = fmt.Sprintf("\"%s\"", depends[i])
}
makeDepends := make([]string, len(bpmpkg.PkgInfo.MakeDepends))
copy(makeDepends, bpmpkg.PkgInfo.MakeDepends)
for i := 0; i < len(makeDepends); i++ {
makeDepends[i] = fmt.Sprintf("\"%s\"", makeDepends[i])
}
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_DEPENDS=(%s)", strings.Join(depends, " ")))
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_PKG_MAKE_DEPENDS=(%s)", strings.Join(makeDepends, " ")))
for _, value := range BPMConfig.CompilationEnv {
cmd.Env = append(cmd.Env, value)
}
cmd.Env = append(cmd.Env, "BPM_PKG_TYPE=source")
if !BPMConfig.SilentCompilation {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
err = cmd.Run()
if err != nil {
return err, nil
}
if _, err := os.Stat(path.Join(temp, "output/")); err != nil {
if os.IsNotExist(err) {
return errors.New("output directory not be found at " + path.Join(temp, "output/")), nil
}
return err, nil
}
if dir, _ := os.ReadDir(path.Join(temp, "output/")); len(dir) == 0 {
return errors.New("output directory is empty"), nil
}
fmt.Println("Copying all files...")
err = filepath.WalkDir(path.Join(temp, "/output/"), func(fullpath string, d fs.DirEntry, err error) error {
relFilename, err := filepath.Rel(path.Join(temp, "/output/"), fullpath)
if relFilename == "." {
return nil
}
extractFilename := path.Join(rootDir, relFilename)
if err != nil {
return err
}
if d.Type() == os.ModeDir {
files = append(files, relFilename+"/")
if err := os.Mkdir(extractFilename, 0755); err != nil {
if !os.IsExist(err) {
return err
}
} else {
if verbose {
fmt.Println("Creating Directory: " + extractFilename)
}
}
} else if d.Type().IsRegular() {
if _, err := os.Stat(extractFilename); err == nil {
if slices.Contains(bpmpkg.PkgInfo.Keep, relFilename) {
if verbose {
fmt.Println("Skipping File: " + extractFilename + "(File is configured to be kept during installs/updates)")
}
files = append(files, relFilename)
return nil
}
}
err := os.Remove(extractFilename)
if err != nil && !os.IsNotExist(err) {
return err
}
outFile, err := os.Create(extractFilename)
if verbose {
fmt.Println("Creating File: " + extractFilename)
}
files = append(files, relFilename)
if err != nil {
return err
}
f, err := os.Open(fullpath)
if err != nil {
return err
}
if _, err := io.Copy(outFile, f); err != nil {
return err
}
info, err := os.Stat(fullpath)
if err != nil {
return err
}
if err := os.Chmod(extractFilename, info.Mode()); err != nil {
return err
}
err = outFile.Close()
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
} else if d.Type() == os.ModeSymlink {
link, err := os.Readlink(fullpath)
if err != nil {
return err
}
err = os.Remove(extractFilename)
if err != nil && !os.IsNotExist(err) {
return err
}
if verbose {
fmt.Println("Creating Symlink: "+extractFilename, " -> "+link)
}
files = append(files, relFilename)
err = os.Symlink(link, extractFilename)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err, nil
}
if binaryPkgFromSrc {
compiledDir := path.Join(BPMConfig.BinaryOutputDir)
err = os.MkdirAll(compiledDir, 0755)
compiledInfo := PackageInfo{}
compiledInfo = *bpmpkg.PkgInfo
compiledInfo.Type = "binary"
compiledInfo.Arch = GetArch()
err = os.WriteFile(path.Join(temp, "pkg.info"), []byte(CreateInfoFile(&compiledInfo)), 0644)
if err != nil {
return err, nil
}
err = os.Chown(path.Join(temp, "pkg.info"), 65534, 65534)
if err != nil {
return err, nil
}
scripts, err := ReadPackageScripts(filename)
for key, val := range scripts {
err = os.WriteFile(path.Join(temp, key), []byte(val), 0644)
if err != nil {
return err, nil
}
err = os.Chown(path.Join(temp, key), 65534, 65534)
if err != nil {
return err, nil
}
}
sed := fmt.Sprintf("s/output/files/")
fileName := compiledInfo.Name + "-" + compiledInfo.GetFullVersion() + "-" + compiledInfo.Arch + ".bpm"
cmd := exec.Command("/usr/bin/fakeroot", "-i fakeroot_file", "tar", "-czvpf", fileName, "pkg.info", "output/", "--transform", sed)
if !BPMConfig.SilentCompilation {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: 65534, Gid: 65534}
cmd.Dir = temp
cmd.Env = os.Environ()
fmt.Printf("running command: %s\n", strings.Join(cmd.Args, " "))
err = cmd.Run()
if err != nil {
return err, nil
}
err = copyFileContents(path.Join(temp, fileName), path.Join(compiledDir, fileName))
if err != nil {
return err, nil
}
err = os.Chown(path.Join(compiledDir, fileName), 0, 0)
if err != nil {
return err, nil
}
}
if !keepTempDir {
err := os.RemoveAll(temp)
if err != nil {
return err, nil
}
}
if len(files) == 0 {
return errors.New("no output files for source package. Cancelling package installation"), nil
}
defer file.Close()
return nil, files
}
func InstallPackage(filename, rootDir string, verbose, force, binaryPkgFromSrc, skipCheck, keepTempDir bool) error {
if _, err := os.Stat(filename); os.IsNotExist(err) {
return err
}
@ -716,7 +1170,13 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
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")
if isSplitPackage(filename) {
return errors.New("BPM is unable to install split source packages")
}
err, _ := compilePackage(bpmpkg, filename, rootDir, verbose, binaryPkgFromSrc, skipCheck, keepTempDir)
if err != nil {
return err
}
} else {
return errors.New("unknown package type: " + bpmpkg.PkgInfo.Type)
}
@ -738,7 +1198,7 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
return err
}
tarballFile, err := readTarballContent(filename, "pkg.files")
tarballFile, err := ReadTarballContent(filename, "pkg.files")
if err != nil {
return err
}
@ -786,12 +1246,12 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
}
if !packageInstalled {
err = executePackageScripts(filename, rootDir, packageOperationInstall, true)
err = ExecutePackageScripts(filename, rootDir, Install, true)
if err != nil {
return err
}
} else {
err = executePackageScripts(filename, rootDir, packageOperationUpdate, true)
err = ExecutePackageScripts(filename, rootDir, Update, true)
if err != nil {
return err
}
@ -848,21 +1308,8 @@ func (pkgInfo *PackageInfo) GetDependants(rootDir string) ([]string, error) {
if bpmpkg == nil {
return nil, errors.New("package not found: " + pkg)
}
if bpmpkg.PkgInfo.Name == pkgInfo.Name {
continue
}
dependencies := bpmpkg.PkgInfo.GetAllDependencies(false, true)
if slices.Contains(dependencies, pkgInfo.Name) {
if bpmpkg.PkgInfo.Name != pkgInfo.Name && slices.Contains(bpmpkg.PkgInfo.GetAllDependencies(false, true), pkgInfo.Name) {
ret = append(ret, pkg)
continue
}
for _, vpkg := range pkgInfo.Provides {
if slices.Contains(dependencies, vpkg) {
ret = append(ret, pkg)
break
}
}
}
@ -1105,7 +1552,7 @@ func GetAllPackageFiles(rootDir string, excludePackages ...string) (map[string][
return ret, nil
}
func removePackage(pkg string, verbose bool, rootDir string) error {
func RemovePackage(pkg string, verbose bool, rootDir string) error {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
pkgInfo := GetPackageInfo(pkg, rootDir)
@ -1189,6 +1636,11 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
// Executing post_remove script
if _, err := os.Stat(path.Join(pkgDir, "post_remove.sh")); err == nil {
cmd := exec.Command("/bin/bash", path.Join(pkgDir, "post_remove.sh"))
if !BPMConfig.SilentCompilation {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
cmd.Dir = rootDir
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, fmt.Sprintf("BPM_ROOT=%s", rootDir))

View File

@ -1,8 +1,7 @@
package bpmlib
package utils
import (
"errors"
"fmt"
"gopkg.in/yaml.v3"
"io"
"net/http"
@ -84,59 +83,29 @@ func (repo *Repository) ReadLocalDatabase() error {
func (repo *Repository) SyncLocalDatabase() error {
repoFile := "/var/lib/bpm/repositories/" + repo.Name + ".bpmdb"
err := os.MkdirAll(path.Dir(repoFile), 0755)
if err != nil {
return err
}
// Get URL to database
u, err := url.JoinPath(repo.Source, "database.bpmdb")
if err != nil {
return err
}
// Retrieve data from URL
resp, err := http.Get(u)
if err != nil {
return err
}
defer resp.Body.Close()
// Load data into byte buffer
buffer, err := io.ReadAll(resp.Body)
// Unmarshal data to ensure it is a valid BPM repository
err = yaml.Unmarshal(buffer, &Repository{})
if err != nil {
return fmt.Errorf("could not decode repository: %s", err)
}
// Create parent directories to repository file
err = os.MkdirAll(path.Dir(repoFile), 0755)
if err != nil {
return err
}
// Create file and save repository data
out, err := os.Create(repoFile)
if err != nil {
return err
}
defer out.Close()
_, err = out.Write(buffer)
return nil
}
func ReadLocalDatabases() (err error) {
for _, repo := range BPMConfig.Repositories {
// Initialize struct values
repo.Entries = make(map[string]*RepositoryEntry)
repo.VirtualPackages = make(map[string][]string)
// Read database
err = repo.ReadLocalDatabase()
if err != nil {
return err
}
}
_, err = io.Copy(out, resp.Body)
return nil
}