Compare commits

..

No commits in common. "master" and "0.3.0" have entirely different histories.

23 changed files with 794 additions and 1823 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 EnumDev
Copyright (c) 2024 CapCreeperGR
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,38 +0,0 @@
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
$(GO) build -ldflags "-w" -o build/bpm gitlab.com/bubble-package-manager/bpm
install: build/bpm config/
mkdir -p $(DESTDIR)$(BINDIR)
mkdir -p $(DESTDIR)$(SYSCONFDIR)
cp build/bpm $(DESTDIR)$(BINDIR)/bpm
cp config/bpm.conf $(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/
.PHONY: build

View File

@ -15,16 +15,15 @@ BPM is still in very early development. It should not be installed on any system
## Build from source
- Download `go` from your package manager or from the go website
- Download `make` from your package manager
- Run the following command to compile the project
```
make
```
- 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
BPM requires go 1.22 or above to be built properly
```sh
git clone https://gitlab.com/bubble-package-manager/bpm.git
cd bpm
mkdir build
go build -o ./build/bpm capcreepergr.me/bpm
```
You are now able to copy the executable in the ./build directory in a VM or container's /usr/bin/ directory
## How to use

29
bpm_utils/config.go Normal file
View File

@ -0,0 +1,29 @@
package bpm_utils
import (
"gopkg.in/yaml.v3"
"os"
)
type BPMConfigStruct struct {
CompilationEnv []string `yaml:"compilation_env"`
SilentCompilation bool `yaml:"silent_compilation"`
}
var BPMConfig BPMConfigStruct = BPMConfigStruct{
CompilationEnv: make([]string, 0),
SilentCompilation: false}
func ReadConfig() {
if _, err := os.Stat("/etc/bpm.conf"); os.IsNotExist(err) {
return
}
bytes, err := os.ReadFile("/etc/bpm.conf")
if err != nil {
return
}
err = yaml.Unmarshal(bytes, &BPMConfig)
if err != nil {
return
}
}

View File

@ -0,0 +1,58 @@
package bpm_utils
import (
"io"
"os"
"os/exec"
"strings"
)
func GetArch() string {
output, err := exec.Command("/usr/bin/uname", "-m").Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(output))
}
func copyFileContents(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
return
}
func stringSliceRemove(s []string, r string) []string {
for i, v := range s {
if v == r {
return append(s[:i], s[i+1:]...)
}
}
return s
}
func stringSliceRemoveEmpty(s []string) []string {
var r []string
for _, str := range s {
if str != "" {
r = append(r, str)
}
}
return r
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
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/
disabled: true

8
go.mod
View File

@ -1,9 +1,5 @@
module gitlab.com/bubble-package-manager/bpm
module capcreepergr.me/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
)
require gopkg.in/yaml.v3 v3.0.1 // indirect

6
go.sum
View File

@ -1,9 +1,3 @@
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/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=

488
main.go
View File

@ -2,9 +2,9 @@ package main
import (
"bufio"
"capcreepergr.me/bpm/bpm_utils"
"flag"
"fmt"
"gitlab.com/bubble-package-manager/bpm/utils"
"log"
"os"
"path/filepath"
@ -12,33 +12,28 @@ import (
"strings"
)
/* -------------BPM | Bubble Package Manager-------------- */
/* Made By EnumDev (Previously CapCreeperGR) */
/* A simple-to-use package manager */
/* ------------------------------------------------------- */
/* ---BPM | Bubble Package Manager--- */
/* Made By CapCreeperGR */
/* A simple-to-use package manager */
/* ---------------------------------- */
var bpmVer = "0.5.0"
var bpmVer = "0.3.0"
var subcommand = "help"
var subcommandArgs []string
// Flags
var rootDir = "/"
var verbose = false
var yesAll = false
var buildSource = false
var skipCheck = false
var keepTempDir = false
var force = false
var forceInstall = false
var pkgListNumbers = false
var pkgListNames = false
var reinstall = false
var reinstallAll = false
var noOptional = false
var nosync = true
func main() {
utils.ReadConfig()
bpm_utils.ReadConfig()
resolveFlags()
resolveCommand()
}
@ -46,14 +41,11 @@ func main() {
type commandType uint8
const (
_default commandType = iota
help
help commandType = iota
version
info
list
search
install
update
sync
remove
file
)
@ -61,19 +53,13 @@ const (
func getCommandType() commandType {
switch subcommand {
case "version":
return _default
return version
case "info":
return info
case "list":
return list
case "search":
return search
case "install":
return install
case "update":
return update
case "sync":
return sync
case "remove":
return remove
case "file":
@ -85,7 +71,7 @@ func getCommandType() commandType {
func resolveCommand() {
switch getCommandType() {
case _default:
case version:
fmt.Println("Bubble Package Manager (BPM)")
fmt.Println("Version: " + bpmVer)
case info:
@ -95,21 +81,20 @@ func resolveCommand() {
return
}
for n, pkg := range packages {
var info *utils.PackageInfo
info = utils.GetPackageInfo(pkg, rootDir)
info := bpm_utils.GetPackageInfo(pkg, rootDir, false)
if info == nil {
log.Fatalf("Error: package (%s) is not installed\n", pkg)
fmt.Printf("Package (%s) could not be found\n", pkg)
continue
}
fmt.Println("----------------")
fmt.Println(utils.CreateReadableInfo(true, true, true, info, rootDir))
fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*info, true))
if n == len(packages)-1 {
fmt.Println("----------------")
}
}
case list:
packages, err := utils.GetInstalledPackages(rootDir)
packages, err := bpm_utils.GetInstalledPackages(rootDir)
if err != nil {
log.Fatalf("Error: could not get installed packages: %s", err.Error())
log.Fatalf("Could not get installed packages\nError: %s", err.Error())
return
}
if pkgListNumbers {
@ -124,275 +109,150 @@ func resolveCommand() {
return
}
for n, pkg := range packages {
info := utils.GetPackageInfo(pkg, rootDir)
info := bpm_utils.GetPackageInfo(pkg, rootDir, false)
if info == nil {
fmt.Printf("Package (%s) could not be found\n", pkg)
continue
}
fmt.Println("----------------\n" + utils.CreateReadableInfo(true, true, true, info, rootDir))
fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*info, true))
if n == len(packages)-1 {
fmt.Println("----------------")
}
}
}
case search:
searchTerms := subcommandArgs
if len(searchTerms) == 0 {
log.Fatalf("Error: no search terms given")
case install:
if os.Getuid() != 0 {
fmt.Println("This subcommand needs to be run with superuser permissions")
os.Exit(0)
}
for _, term := range searchTerms {
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)
} else if strings.Contains(entry.Info.Description, term) {
descResults = append(descResults, entry.Info)
files := subcommandArgs
if len(files) == 0 {
fmt.Println("No files were given to install")
return
}
for _, file := range files {
pkgInfo, err := bpm_utils.ReadPackage(file)
if err != nil {
log.Fatalf("Could not read package\nError: %s\n", err)
}
fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*pkgInfo, true))
fmt.Println("----------------")
verb := "install"
if pkgInfo.Type == "source" {
verb = "build"
}
if !forceInstall {
if pkgInfo.Arch != "any" && pkgInfo.Arch != bpm_utils.GetArch() {
fmt.Printf("skipping... cannot %s a package with a different architecture\n", verb)
continue
}
if unresolved := bpm_utils.CheckDependencies(pkgInfo, rootDir); len(unresolved) != 0 {
fmt.Printf("skipping... cannot %s package (%s) due to missing dependencies: %s\n", verb, pkgInfo.Name, strings.Join(unresolved, ", "))
continue
}
if pkgInfo.Type == "source" {
if unresolved := bpm_utils.CheckMakeDependencies(pkgInfo, "/"); len(unresolved) != 0 {
fmt.Printf("skipping... cannot %s package (%s) due to missing make dependencies: %s\n", verb, pkgInfo.Name, strings.Join(unresolved, ", "))
continue
}
}
}
results := append(nameResults, descResults...)
if len(results) == 0 {
log.Fatalf("Error: no results for term (%s) were found\n", term)
if rootDir != "/" {
fmt.Println("Warning: Operating in " + rootDir)
}
fmt.Printf("Results for term (%s)\n", term)
for i, result := range results {
fmt.Println("----------------")
fmt.Printf("%d) %s: %s (%s)\n", i+1, result.Name, result.Description, result.GetFullVersion())
}
}
case install:
if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
}
pkgs := subcommandArgs
if len(pkgs) == 0 {
fmt.Println("No packages or files were given to install")
return
}
operation := utils.BPMOperation{
Actions: make([]utils.OperationAction, 0),
UnresolvedDepends: make([]string, 0),
RootDir: rootDir,
}
// 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 package: %s\n", err)
if !yesAll {
reader := bufio.NewReader(os.Stdin)
if pkgInfo.Type == "source" {
fmt.Print("Would you like to view the source.sh file of this package? [Y\\n] ")
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "n" && strings.TrimSpace(strings.ToLower(text)) != "no" {
script, err := bpm_utils.GetSourceScript(file)
if err != nil {
log.Fatalf("Could not read source script\nError: %s\n", err)
}
fmt.Println(script)
fmt.Println("-------EOF-------")
}
}
if !reinstall && utils.IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && utils.GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() {
}
if bpm_utils.IsPackageInstalled(pkgInfo.Name, rootDir) {
if !yesAll {
installedInfo := bpm_utils.GetPackageInfo(pkgInfo.Name, rootDir, false)
if strings.Compare(pkgInfo.Version, installedInfo.Version) > 0 {
fmt.Println("This file contains a newer version of this package (" + installedInfo.Version + " -> " + pkgInfo.Version + ")")
fmt.Print("Do you wish to update this package? [y\\N] ")
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) < 0 {
fmt.Println("This file contains an older version of this package (" + installedInfo.Version + " -> " + pkgInfo.Version + ")")
fmt.Print("Do you wish to downgrade this package? (Not recommended) [y\\N] ")
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) == 0 {
fmt.Println("This package is already installed on the system and is up to date")
fmt.Printf("Do you wish to re%s this package? [y\\N] ", verb)
}
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Printf("Skipping package (%s)...\n", pkgInfo.Name)
continue
}
}
} else if !yesAll {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("Do you wish to %s this package? [y\\N] ", verb)
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Printf("Skipping package (%s)...\n", pkgInfo.Name)
continue
}
operation.Actions = append(operation.Actions, &utils.InstallPackageAction{
File: pkg,
IsDependency: false,
BpmPackage: bpmpkg,
})
} else {
entry, _, err := utils.GetRepositoryEntry(pkg)
if err != nil {
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.Actions = append(operation.Actions, &utils.FetchPackageAction{
IsDependency: false,
RepositoryEntry: entry,
})
}
}
// 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, ", "))
}
}
// Show operation summary
operation.ShowOperationSummary()
// Confirmation Prompt
if !yesAll {
reader := bufio.NewReader(os.Stdin)
if len(operation.Actions) == 1 {
fmt.Printf("Do you wish to install this package? [y\\N] ")
} else {
fmt.Printf("Do you wish to install these %d packages? [y\\N] ", len(operation.Actions))
}
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling package installation...")
os.Exit(1)
}
}
// Execute operation
err = operation.Execute(verbose, force)
if err != nil {
log.Fatalf("Error: could not complete operation: %s\n", err)
}
case update:
if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
}
// 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 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),
RootDir: rootDir,
}
// Search for packages
for _, pkg := range pkgs {
entry, _, err := utils.GetRepositoryEntry(pkg)
err = bpm_utils.InstallPackage(file, rootDir, forceInstall, buildSource, skipCheck, keepTempDir)
if 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.Actions = append(operation.Actions, &utils.FetchPackageAction{
IsDependency: false,
RepositoryEntry: entry,
})
if pkgInfo.Type == "source" && keepTempDir {
fmt.Println("BPM temp directory was created at /var/tmp/bpm_source-" + pkgInfo.Name)
}
log.Fatalf("Could not install package\nError: %s\n", err)
}
fmt.Printf("Package (%s) was successfully installed!\n", pkgInfo.Name)
if pkgInfo.Type == "source" && keepTempDir {
fmt.Println("** It is recommended you delete the temporary bpm folder in /var/tmp **")
}
}
// 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, ", "))
}
}
// Show operation summary
operation.ShowOperationSummary()
// Confirmation Prompt
if !yesAll {
fmt.Printf("Are you sure you wish to update all %d packages? [y\\N] ", len(operation.Actions))
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling package update...")
os.Exit(1)
}
}
// Execute operation
err = operation.Execute(verbose, force)
if err != nil {
log.Fatalf("Error: could not complete operation: %s\n", err)
}
case sync:
if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
}
if !yesAll {
fmt.Printf("Are you sure you wish to sync all databases? [y\\N] ")
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling database synchronization...")
os.Exit(1)
}
}
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 for repository (%s): %s\n", repo.Name, err)
}
}
fmt.Println("All package databases synced successfully!")
case remove:
if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
fmt.Println("This subcommand needs to be run with superuser permissions")
os.Exit(0)
}
packages := subcommandArgs
if len(packages) == 0 {
fmt.Println("No packages were given")
return
}
operation := &utils.BPMOperation{
Actions: make([]utils.OperationAction, 0),
UnresolvedDepends: make([]string, 0),
RootDir: rootDir,
}
// Search for packages
for _, pkg := range packages {
bpmpkg := utils.GetPackage(pkg, rootDir)
if bpmpkg == nil {
log.Fatalf("Error: package (%s) could not be found\n", pkg)
pkgInfo := bpm_utils.GetPackageInfo(pkg, rootDir, false)
if pkgInfo == nil {
fmt.Printf("Package (%s) could not be found\n", pkg)
continue
}
operation.Actions = append(operation.Actions, &utils.RemovePackageAction{BpmPackage: bpmpkg})
}
// Show operation summary
operation.ShowOperationSummary()
// Confirmation Prompt
if !yesAll {
fmt.Printf("Are you sure you wish to remove all %d packages? [y\\N] ", len(operation.Actions))
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling package removal...")
os.Exit(1)
fmt.Print("----------------\n" + bpm_utils.CreateInfoFile(*pkgInfo, true))
fmt.Println("----------------")
if rootDir != "/" {
fmt.Println("Warning: Operating in " + rootDir)
}
}
if !yesAll {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Do you wish to remove this package? [y\\N] ")
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Printf("Skipping package (%s)...\n", pkgInfo.Name)
continue
}
}
err := bpm_utils.RemovePackage(pkg, rootDir)
// Execute operation
err := operation.Execute(verbose, force)
if err != nil {
log.Fatalf("Error: could not complete operation: %s\n", err)
if err != nil {
log.Fatalf("Could not remove package\nError: %s\n", err)
}
fmt.Printf("Package (%s) was successfully removed!\n", pkgInfo.Name)
}
case file:
files := subcommandArgs
@ -403,23 +263,23 @@ func resolveCommand() {
for _, file := range files {
absFile, err := filepath.Abs(file)
if err != nil {
log.Fatalf("Error: could not get absolute path of file (%s)\n", file)
log.Fatalf("Could not get absolute path of %s", file)
}
stat, err := os.Stat(absFile)
if os.IsNotExist(err) {
log.Fatalf("Error: file (%s) does not exist!\n", absFile)
log.Fatalf(absFile + " does not exist!")
}
pkgs, err := utils.GetInstalledPackages(rootDir)
pkgs, err := bpm_utils.GetInstalledPackages(rootDir)
if err != nil {
log.Fatalf("Error: could not get installed packages: %s\n", err.Error())
log.Fatalf("Could not get installed packages. Error %s", err.Error())
}
if !strings.HasPrefix(absFile, rootDir) {
log.Fatalf("Error: could not get path of file (%s) relative to root path", absFile)
log.Fatalf("Could not get relative path of %s to root path", absFile)
}
absFile, err = filepath.Rel(rootDir, absFile)
if err != nil {
log.Fatalf("Error: could not get path of file (%s) relative to root path", absFile)
log.Fatalf("Could not get relative path of %s to root path", absFile)
}
absFile = strings.TrimPrefix(absFile, "/")
if stat.IsDir() {
@ -428,9 +288,7 @@ func resolveCommand() {
var pkgList []string
for _, pkg := range pkgs {
if slices.ContainsFunc(utils.GetPackageFiles(pkg, rootDir), func(entry *utils.PackageFileEntry) bool {
return entry.Path == absFile
}) {
if slices.Contains(bpm_utils.GetPackageFiles(pkg, rootDir), absFile) {
pkgList = append(pkgList, pkg)
}
}
@ -454,39 +312,20 @@ func printHelp() {
fmt.Println("-> flags will be read if passed right after the subcommand otherwise they will be read as subcommand arguments")
fmt.Println("\033[1m---- Command List ----\033[0m")
fmt.Println("-> bpm version | shows information on the installed version of bpm")
fmt.Println("-> bpm info [-R] <packages...> | shows information on an installed package")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println("-> bpm info [-R] | shows information on an installed package")
fmt.Println(" -R=<root_path> lets you define the root path which will be used")
fmt.Println("-> bpm list [-R, -c, -n] | lists all installed packages")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -R=<root_path> lets you define the root path which will be used")
fmt.Println(" -c lists the amount of installed packages")
fmt.Println(" -n lists only the names of installed packages")
fmt.Println("-> bpm search <search terms...> | Searches for packages through declared repositories")
fmt.Println("-> bpm install [-R, -v, -y, -f, -o, -c, -b, -k, --reinstall, --reinstall-all, --no-optional] <packages...> | installs the following files")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println("-> bpm install [-R, -y, -f, -b] <files...> | installs the following files")
fmt.Println(" -R=<root_path> lets you define the root path which will be used")
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")
fmt.Println(" --no-optional Prevents installation of optional dependencies")
fmt.Println("-> bpm update [-R, -v, -y, -f, --reinstall, --no-sync] | updates all packages that are available in the repositories")
fmt.Println(" -R=<path> lets you define the root path which will be used")
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(" --reinstall Fetches and reinstalls all packages even if they do not have a newer version available")
fmt.Println(" --no-sync Skips package database syncing")
fmt.Println("-> bpm sync [-R, -v, -y] | Syncs package databases without updating packages")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt")
fmt.Println("-> bpm remove [-R, -v, -y] <packages...> | removes the following 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")
fmt.Println(" -f skips dependency and architecture checking")
fmt.Println(" -b creates a binary package for a source package after compilation and saves it in /var/lib/bpm/compiled")
fmt.Println(" -k keeps the temp directory created by BPM after source package installation")
fmt.Println("-> bpm remove [-R, -y] <packages...> | removes the following packages")
fmt.Println(" -R=<root_path> lets you define the root path which will be used")
fmt.Println(" -y skips the confirmation prompt")
fmt.Println("-> bpm file [-R] <files...> | shows what packages the following packages are managed by")
fmt.Println(" -R=<root_path> lets you define the root path which will be used")
@ -507,37 +346,15 @@ func resolveFlags() {
// Install flags
installFlagSet := flag.NewFlagSet("Install flags", flag.ExitOnError)
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")
installFlagSet.BoolVar(&noOptional, "no-optional", false, "Prevents installation of optional dependencies")
installFlagSet.BoolVar(&forceInstall, "f", false, "Force installation by skipping architecture and dependency resolution")
installFlagSet.Usage = printHelp
// Update flags
updateFlagSet := flag.NewFlagSet("Update flags", flag.ExitOnError)
updateFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
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
syncFlagSet := flag.NewFlagSet("Sync flags", flag.ExitOnError)
syncFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
syncFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
syncFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
syncFlagSet.Usage = printHelp
// Remove flags
removeFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError)
removeFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
removeFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
removeFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
removeFlagSet.Usage = printHelp
// File flags
@ -567,18 +384,6 @@ func resolveFlags() {
return
}
subcommandArgs = installFlagSet.Args()
} else if getCommandType() == update {
err := updateFlagSet.Parse(subcommandArgs)
if err != nil {
return
}
subcommandArgs = updateFlagSet.Args()
} else if getCommandType() == sync {
err := syncFlagSet.Parse(subcommandArgs)
if err != nil {
return
}
subcommandArgs = syncFlagSet.Args()
} else if getCommandType() == remove {
err := removeFlagSet.Parse(subcommandArgs)
if err != nil {
@ -592,8 +397,5 @@ func resolveFlags() {
}
subcommandArgs = fileFlagSet.Args()
}
if reinstallAll {
reinstall = true
}
}
}

Binary file not shown.

View File

@ -0,0 +1,3 @@
---
compilation_env: []
silent_compilation: false

Binary file not shown.

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 CapCreeperGR
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,8 @@
name: bpm
description: The Bubble Package Manager
version: 0.3.0
url: https://gitlab.com/bubble-package-manager/bpm/
license: GPL3
architecture: x86_64
type: binary
keep: etc/bpm.conf

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
name: hello
description: A simple hello world program
version: 1.0
architecture: x86_64
type: binary

View File

@ -1,49 +0,0 @@
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"`
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)
err := repo.ReadLocalDatabase()
if err != nil {
log.Fatal(err)
}
}
}

View File

@ -1,43 +0,0 @@
package utils
import (
"archive/tar"
"errors"
"io"
"os"
)
type TarballFileReader struct {
tarReader *tar.Reader
file *os.File
}
func ReadTarballContent(tarballPath, fileToExtract string) (*TarballFileReader, error) {
file, err := os.Open(tarballPath)
if err != nil {
return nil, err
}
tr := tar.NewReader(file)
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if header.Name == fileToExtract {
if header.Typeflag != tar.TypeReg {
return nil, errors.New("file to extract must be a regular file")
}
return &TarballFileReader{
tarReader: tr,
file: file,
}, nil
}
}
return nil, errors.New("could not file in tarball")
}

View File

@ -1,78 +0,0 @@
package utils
import (
"fmt"
"io"
"math"
"os"
"syscall"
)
func GetArch() string {
uname := syscall.Utsname{}
err := syscall.Uname(&uname)
if err != nil {
return ""
}
var byteString [65]byte
var indexLength int
for ; uname.Machine[indexLength] != 0; indexLength++ {
byteString[indexLength] = uint8(uname.Machine[indexLength])
}
return string(byteString[:indexLength])
}
func copyFileContents(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
return
}
func stringSliceRemove(s []string, r string) []string {
for i, v := range s {
if v == r {
return append(s[:i], s[i+1:]...)
}
}
return s
}
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 {
return fmt.Sprintf("%3.1f%sB", bf, unit)
}
bf /= 1024.0
}
return fmt.Sprintf("%.1fYiB", bf)
}
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 {
return fmt.Sprintf("%3.1f%sB", bf, unit)
}
bf /= 1024.0
}
return fmt.Sprintf("%.1fYiB", bf)
}

View File

@ -1,289 +0,0 @@
package utils
import (
"errors"
"fmt"
"log"
"os"
"slices"
"strings"
)
type BPMOperation struct {
Actions []OperationAction
UnresolvedDepends []string
RootDir string
}
func (operation *BPMOperation) ActionsContainPackage(pkg string) bool {
for _, action := range operation.Actions {
if action.GetActionType() == "install" {
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg {
return true
}
} else if action.GetActionType() == "fetch" {
if action.(*FetchPackageAction).RepositoryEntry.Info.Name == pkg {
return true
}
} else if action.GetActionType() == "remove" {
if action.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg {
return true
}
}
}
return false
}
func (operation *BPMOperation) InsertActionAt(index int, action OperationAction) {
if len(operation.Actions) == index { // nil or empty slice or after last element
operation.Actions = append(operation.Actions, action)
}
operation.Actions = append(operation.Actions[:index+1], operation.Actions[index:]...) // index < len(a)
operation.Actions[index] = action
}
func (operation *BPMOperation) GetTotalDownloadSize() uint64 {
var ret uint64 = 0
for _, action := range operation.Actions {
if action.GetActionType() == "fetch" {
ret += action.(*FetchPackageAction).RepositoryEntry.DownloadSize
}
}
return ret
}
func (operation *BPMOperation) GetTotalInstalledSize() uint64 {
var ret uint64 = 0
for _, action := range operation.Actions {
if action.GetActionType() == "install" {
ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize()
} else if action.GetActionType() == "fetch" {
ret += action.(*FetchPackageAction).RepositoryEntry.InstalledSize
}
}
return ret
}
func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
var ret int64 = 0
for _, action := range operation.Actions {
if action.GetActionType() == "install" {
ret += int64(action.(*InstallPackageAction).BpmPackage.GetInstalledSize())
if IsPackageInstalled(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir) {
ret -= int64(GetPackage(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir).GetInstalledSize())
}
} else if action.GetActionType() == "fetch" {
ret += int64(action.(*FetchPackageAction).RepositoryEntry.InstalledSize)
} else if action.GetActionType() == "remove" {
ret -= int64(action.(*RemovePackageAction).BpmPackage.GetInstalledSize())
}
}
return ret
}
func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, installOptionalDependencies, verbose bool) error {
pos := 0
for _, value := range slices.Clone(operation.Actions) {
var pkgInfo *PackageInfo
if value.GetActionType() == "install" {
action := value.(*InstallPackageAction)
pkgInfo = action.BpmPackage.PkgInfo
} else if value.GetActionType() == "fetch" {
action := value.(*FetchPackageAction)
pkgInfo = action.RepositoryEntry.Info
} else {
pos++
continue
}
resolved, unresolved := pkgInfo.ResolveDependencies(&[]string{}, &[]string{}, pkgInfo.Type == "source", installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir)
operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...)
for _, depend := range resolved {
if !operation.ActionsContainPackage(depend) && depend != pkgInfo.Name {
if !reinstallDependencies && IsPackageInstalled(depend, operation.RootDir) {
continue
}
entry, _, err := GetRepositoryEntry(depend)
if err != nil {
return errors.New("could not get repository entry for package (" + depend + ")")
}
operation.InsertActionAt(pos, &FetchPackageAction{
IsDependency: true,
RepositoryEntry: entry,
})
pos++
}
}
pos++
}
return nil
}
func (operation *BPMOperation) ShowOperationSummary() {
if len(operation.Actions) == 0 {
fmt.Println("All packages are up to date!")
os.Exit(0)
}
for _, value := range operation.Actions {
var pkgInfo *PackageInfo
if value.GetActionType() == "install" {
pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo
} else if value.GetActionType() == "fetch" {
pkgInfo = value.(*FetchPackageAction).RepositoryEntry.Info
} else {
pkgInfo = value.(*RemovePackageAction).BpmPackage.PkgInfo
fmt.Printf("%s: %s (Remove)\n", pkgInfo.Name, pkgInfo.GetFullVersion())
continue
}
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)"
}
if installedInfo == nil {
fmt.Printf("%s: %s (Install) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), sourceInfo)
} else {
comparison := ComparePackageVersions(*pkgInfo, *installedInfo)
if comparison < 0 {
fmt.Printf("%s: %s -> %s (Downgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), sourceInfo)
} else if comparison > 0 {
fmt.Printf("%s: %s -> %s (Upgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), sourceInfo)
} else {
fmt.Printf("%s: %s (Reinstall) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), sourceInfo)
}
}
}
if operation.RootDir != "/" {
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()))
}
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)))
} 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)), "-"))
}
}
func (operation *BPMOperation) Execute(verbose, force bool) error {
// Fetch packages from repositories
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
return action.GetActionType() == "fetch"
}) {
fmt.Println("Fetching packages from available repositories...")
for i, action := range operation.Actions {
if action.GetActionType() != "fetch" {
continue
}
entry := action.(*FetchPackageAction).RepositoryEntry
fetchedPackage, err := entry.Repository.FetchPackage(entry.Info.Name)
if err != nil {
return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err))
}
bpmpkg, err := ReadPackage(fetchedPackage)
if err != nil {
return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err))
}
fmt.Printf("Package (%s) was successfully fetched!\n", bpmpkg.PkgInfo.Name)
operation.Actions[i] = &InstallPackageAction{
File: fetchedPackage,
IsDependency: action.(*FetchPackageAction).IsDependency,
BpmPackage: bpmpkg,
}
}
}
// Determine words to be used for the following message
words := make([]string, 0)
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
return action.GetActionType() == "install"
}) {
words = append(words, "Installing")
}
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
return action.GetActionType() == "remove"
}) {
words = append(words, "Removing")
}
if len(words) == 0 {
return nil
}
fmt.Printf("%s packages...\n", strings.Join(words, "/"))
// Installing/Removing packages from system
for _, action := range operation.Actions {
if action.GetActionType() == "remove" {
pkgInfo := action.(*RemovePackageAction).BpmPackage.PkgInfo
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))
}
} else if action.GetActionType() == "install" {
value := action.(*InstallPackageAction)
bpmpkg := value.BpmPackage
var err error
if value.IsDependency {
err = InstallPackage(value.File, operation.RootDir, verbose, true, false, false, false)
} else {
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))
}
fmt.Printf("Package (%s) was successfully installed\n", bpmpkg.PkgInfo.Name)
if value.IsDependency {
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))
}
}
}
}
fmt.Println("Operation complete!")
return nil
}
type OperationAction interface {
GetActionType() string
}
type InstallPackageAction struct {
File string
IsDependency bool
BpmPackage *BPMPackage
}
func (action *InstallPackageAction) GetActionType() string {
return "install"
}
type FetchPackageAction struct {
IsDependency bool
RepositoryEntry *RepositoryEntry
}
func (action *FetchPackageAction) GetActionType() string {
return "fetch"
}
type RemovePackageAction struct {
BpmPackage *BPMPackage
}
func (action *RemovePackageAction) GetActionType() string {
return "remove"
}

View File

@ -1,197 +0,0 @@
package utils
import (
"errors"
"gopkg.in/yaml.v3"
"io"
"net/http"
"net/url"
"os"
"path"
"sort"
"strings"
)
type Repository struct {
Name string `yaml:"name"`
Source string `yaml:"source"`
Disabled *bool `yaml:"disabled"`
Entries map[string]*RepositoryEntry
}
type RepositoryEntry struct {
Info *PackageInfo `yaml:"info"`
Download string `yaml:"download"`
DownloadSize uint64 `yaml:"download_size"`
InstalledSize uint64 `yaml:"installed_size"`
IsVirtualPackage bool `yaml:"-"`
Repository *Repository
}
func (repo *Repository) ContainsPackage(pkg string) bool {
_, ok := repo.Entries[pkg]
return ok
}
func (repo *Repository) ReadLocalDatabase() error {
repoFile := "/var/lib/bpm/repositories/" + repo.Name + ".bpmdb"
if _, err := os.Stat(repoFile); err != nil {
return nil
}
bytes, err := os.ReadFile(repoFile)
if err != nil {
return err
}
virtualPackages := make(map[string][]string)
data := string(bytes)
for _, b := range strings.Split(data, "---") {
entry := RepositoryEntry{
Info: &PackageInfo{
Name: "",
Description: "",
Version: "",
Revision: 1,
Url: "",
License: "",
Arch: "",
Type: "",
Keep: make([]string, 0),
Depends: make([]string, 0),
MakeDepends: make([]string, 0),
OptionalDepends: make([]string, 0),
Conflicts: make([]string, 0),
Provides: make([]string, 0),
},
Download: "",
DownloadSize: 0,
InstalledSize: 0,
IsVirtualPackage: false,
Repository: repo,
}
err := yaml.Unmarshal([]byte(b), &entry)
if err != nil {
return err
}
for _, p := range entry.Info.Provides {
virtualPackages[p] = append(virtualPackages[p], entry.Info.Name)
}
repo.Entries[entry.Info.Name] = &entry
}
for key, value := range virtualPackages {
if _, ok := repo.Entries[key]; ok {
continue
}
sort.Strings(value)
entry := RepositoryEntry{
Info: repo.Entries[value[0]].Info,
Download: repo.Entries[value[0]].Download,
DownloadSize: repo.Entries[value[0]].DownloadSize,
InstalledSize: repo.Entries[value[0]].InstalledSize,
IsVirtualPackage: true,
Repository: repo,
}
repo.Entries[key] = &entry
}
return nil
}
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
}
u, err := url.JoinPath(repo.Source, "database.bpmdb")
if err != nil {
return err
}
resp, err := http.Get(u)
if err != nil {
return err
}
defer resp.Body.Close()
out, err := os.Create(repoFile)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return nil
}
func GetRepository(name string) *Repository {
for _, repo := range BPMConfig.Repositories {
if repo.Name == name {
return repo
}
}
return nil
}
func GetRepositoryEntry(str string) (*RepositoryEntry, *Repository, error) {
split := strings.Split(str, "/")
if len(split) == 1 {
pkgName := strings.TrimSpace(split[0])
if pkgName == "" {
return nil, nil, errors.New("could not find repository entry for this package")
}
for _, repo := range BPMConfig.Repositories {
if repo.ContainsPackage(pkgName) {
return repo.Entries[pkgName], repo, nil
}
}
return nil, nil, errors.New("could not find repository entry for this package")
} else if len(split) == 2 {
repoName := strings.TrimSpace(split[0])
pkgName := strings.TrimSpace(split[1])
if repoName == "" || pkgName == "" {
return nil, nil, errors.New("could not find repository entry for this package")
}
repo := GetRepository(repoName)
if repo == nil || !repo.ContainsPackage(pkgName) {
return nil, nil, errors.New("could not find repository entry for this package")
}
return repo.Entries[pkgName], repo, nil
} else {
return nil, nil, errors.New("could not find repository entry for this package")
}
}
func (repo *Repository) FetchPackage(pkg string) (string, error) {
if !repo.ContainsPackage(pkg) {
return "", errors.New("could not fetch package '" + pkg + "'")
}
entry := repo.Entries[pkg]
URL, err := url.JoinPath(repo.Source, entry.Download)
if err != nil {
return "", err
}
resp, err := http.Get(URL)
if err != nil {
return "", err
}
defer resp.Body.Close()
err = os.MkdirAll("/var/cache/bpm/packages/", 0755)
if err != nil {
return "", err
}
out, err := os.Create("/var/cache/bpm/packages/" + path.Base(entry.Download))
if err != nil {
return "", err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return "/var/cache/bpm/packages/" + path.Base(entry.Download), nil
}