46 Commits
Author SHA1 Message Date
EnumDev 4217626dde Update passed flag to bpm 2025-10-05 20:00:05 +03:00
EnumDevandGitHub d7da13034c Merge pull request #1 from EnumeratedDev/go_rewrite
Rewrite bpm-utils in go
2025-10-05 09:37:55 +00:00
EnumDev cdb044d751 Update pkg.info format 2025-10-05 12:14:54 +03:00
EnumDev 60b0f4489e Improve git branch checksum fetching 2025-10-05 11:00:15 +03:00
EnumDev 65c32d628f Update repository version checking messages 2025-10-03 14:18:53 +03:00
EnumDev 4ff4a74fa3 Improve package info parsing 2025-09-29 10:35:54 +03:00
EnumDev f6ef60aa40 Add cargo template 2025-09-28 18:24:35 +03:00
EnumDev 9625b5d7af Use drone/envsubst module for bash variable expansion 2025-09-28 12:39:30 +03:00
EnumDev d881ac31c3 Update README.md 2025-09-27 21:32:07 +03:00
EnumDev 96a39a6b40 Update default pkg.info content 2025-09-27 21:19:13 +03:00
EnumDev 7a63ad6388 Update templates 2025-09-27 21:18:39 +03:00
EnumDev 10f918034a Add 'update-checksums' flag 2025-09-27 13:39:54 +03:00
EnumDev 59c72d1a40 Add missing newline character 2025-09-21 11:04:20 +03:00
EnumDev 61c01ae88f Add 'print-errorlogs' flag to meson template 2025-09-10 18:52:10 +03:00
EnumDev 0fdef74c73 Update cmake and meson templates 2025-09-10 18:16:25 +03:00
EnumDev d5abad3371 Add 'ignored' status for package version checking 2025-09-09 19:05:57 +03:00
EnumDev 334eb15c94 Improve flag system 2025-09-09 18:59:28 +03:00
EnumDev 7aff954df4 Fix moved BPM archive being removed by bpm-package 2025-09-08 22:12:44 +03:00
EnumDev 2197f78bc0 Add 'check-versions' flag to bpm-repo 2025-09-08 10:16:23 +03:00
EnumDev 1b6585424f Remove old BPM archives when creating new one 2025-09-05 20:56:57 +03:00
EnumDev c9627e1068 Add gitignore template 2025-09-05 20:46:12 +03:00
EnumDev aae5299b8a Add 'list packages' flag to bpm-repo 2025-09-05 19:42:33 +03:00
EnumDev 908b9ccc12 Update README.md 2025-09-04 19:16:10 +03:00
EnumDev 162f76b697 Add flag to bpm-package for moving output packages to the binary
directory
2025-09-04 18:08:12 +03:00
EnumDev f656006710 Add bpm-repo functionality 2025-09-04 18:07:44 +03:00
EnumDev 1d43614fde Remove bpm-convert 2025-09-04 13:02:39 +03:00
EnumDev ad691cd78e Switch from Go's flag package to pflag 2025-09-04 12:21:23 +03:00
EnumDev 3cfb314333 Improve package compilation and installation handling 2025-09-04 12:01:59 +03:00
EnumDev 3b5c8de115 Update README.md 2025-09-04 12:01:09 +03:00
EnumDev 3dbb24fd15 Update Makefile 2025-09-03 18:36:46 +03:00
EnumDev 5a7995d226 Move help message and flag setup to bpm-utils-shared package 2025-05-04 17:52:29 +03:00
EnumDev a6578fb414 Remove snake-case from bpm-utils-shared import 2025-05-04 17:33:44 +03:00
EnumDev 354d284289 Make Makefile POSIX-compliant 2025-05-04 17:32:23 +03:00
EnumDev 4235252172 Add bpm-package functionality 2025-05-04 17:29:00 +03:00
EnumDev 2dec7c28e6 Update README.md 2025-04-09 17:25:55 +03:00
EnumDev 368c599ea6 Replace placeholders in template files 2025-04-09 13:51:46 +03:00
EnumDev e147d74137 Add command description to bpm-setup help message 2025-04-08 14:16:32 +03:00
EnumDev d395004658 Add a few template files 2025-04-08 14:11:57 +03:00
EnumDev a0aff54e95 Fix error message 2025-04-08 13:48:16 +03:00
EnumDev e021e814f4 Trim spaces from directory path and package name 2025-04-08 13:47:56 +03:00
EnumDev 57a58a18a7 Add checks to bpm-setup 2025-04-08 13:43:55 +03:00
EnumDev 23b5f0eee1 Add bpm-setup functionality 2025-04-07 22:05:04 +03:00
EnumDev c60bec2c1a Setup flag system for bpm-setup 2025-04-07 21:40:32 +03:00
EnumDev e02d64b67e Setup basic repository structure 2025-04-07 21:22:24 +03:00
EnumDev 5076c48001 Update LICENSE 2025-04-07 21:10:56 +03:00
EnumDev 7df29661c3 Moved over to new BPM file format 2024-10-26 18:04:04 +03:00
31 changed files with 1547 additions and 605 deletions
+5
View File
@@ -0,0 +1,5 @@
# IDE Directories
.idea
# Build directory
build
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 CapCreeperGR
Copyright (c) 2024 EnumDev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+35
View File
@@ -0,0 +1,35 @@
# Installation paths
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
SYSCONFDIR := $(PREFIX)/etc
# Compilers and tools
GO ?= go
build:
mkdir -p build
cd src/bpm-package; $(GO) build -ldflags "-w" -o ../../build/bpm-package git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-package
cd src/bpm-repo; $(GO) build -ldflags "-w" -o ../../build/bpm-repo git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-repo
cd src/bpm-setup; $(GO) build -ldflags "-w" -o ../../build/bpm-setup git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-setup
install:
# Create directory
install -dm755 $(DESTDIR)$(BINDIR)
# Install binary
install -Dm755 build/bpm-* -t $(DESTDIR)$(BINDIR)/
install-config:
# Create directory
install -dm755 $(DESTDIR)$(SYSCONFDIR)
# Install files
install -dm755 $(DESTDIR)$(SYSCONFDIR)/bpm-utils/
cp -r config/* -t $(DESTDIR)$(SYSCONFDIR)/bpm-utils/
uninstall:
-rm -f $(DESTDIR)$(BINDIR)/bpm-{package,repo,setup}
-rm -rf $(DESTDIR)$(SYSCONFDIR)/bpm-utils/
clean:
rm -r build/
.PHONY: build install install-config uninstall clean
+49 -35
View File
@@ -1,56 +1,70 @@
# BPM Utils
### _Package creation and editing utilities for BPM_ ###
### _Creation and maintenance utilities for BPM packages and repositories_ ###
## Information
BPM Utils provides a number of different helper commands for creating and maintaining BPM packages or repositories
BPM Utils is a package providing a number of different helper scripts for setting up and archiving BPM packages
## Provided Scripts
- bpm-setup (Creates a directory with the required files for a BPM package)
- bpm-package (Turns a BPM package directory into a .bpm archive)
- bpm-convert (Converts source packages to binary ones)
- create-repository-data (Generates a repository package data list for unpac)
## Provided utilities
- bpm-setup (Sets up directories for BPM source package creation)
- bpm-repo (Allows for easy management of multiple-package repositories)
- bpm-package (Turns a BPM source package directory into a .bpm archive)
## Installation
Currently all BPM Utilities are simple bash scripts. This means you are able to simply clone this repository and place these scripts wherever you would like
#### Using a package manager
- Tide Linux: Tide linux provides a `bpm-utils` package which can be installed using `bpm install bpm-utils`
#### Building 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 bpm-utils to your system. You may also append a DESTDIR variable at the end of this line if you wish to install the files to a different location
```
make install PREFIX=/usr SYSCONFDIR=/etc
make install-config PREFIX=/usr SYSCONFDIR=/etc
```
## Package Creation using BPM Utils
Creating a package for BPM with these utilities is simple
2) Run the following command (You can run the comamnd with no arguments to see available options)
1) Run the following command (You can run the command with no arguments to see all available options)
```
bpm-setup -D my_package -t <binary/source>
```
3) This will create a directory named 'my_bpm_package' under the current directory with all the required files for the chosen package type
4) You are able to edit the pkg.info descriptor file inside the newly created directory to your liking. Here's an example of what a descriptor file could look like
bpm-setup -D my_package
```
2) This will create a directory named `my_package` containing all files required for bpm package creation
3) You may wish to edit the pkg.info metedata file inside the newly created directory to include dependencies or add/change other information. Here's an example of what a metedata file could look like
```yaml
name: my_package
description: My package's description
version: 1.0
revision: 2 (Optional)
architecture: x86_64
url: https://www.my-website.com/ (Optional)
license: MyLicense (Optional)
type: <binary/source>
depends: ["dependency1","dependency2"] (Optional)
optional_depends: ["optional_depend1","optional_depend2"]
make_depends: ["make_depend1","make_depend2"] (Optional)
architecture: x86_64
type: source
depends:
- dependency1
- dependency2
optional_depends:
- optional_depend1
- optional_depend2
make_depends:
- make_depend1
- make_depend2
keep:
- etc/my_config.conf
downloads:
- url: https://wwww.my-url.com/file.tar.gz
extract_strip_components: 1
extract_to_bpm_source: true
checksum: 9d19c8884cb22a594ba06a4caa6a3088e15ddfd4f3ede8c3b9e8f5cbb5a4a7a8
```
### Binary Packages
3) If you are making a binary package, copy all your binaries along with the directories they reside in (i.e files/usr/bin/my_binary)
6) Run the following to create a package archive
4) If you would like to bundle patches or other files with your package place them in the 'source-files' directory. They will be extracted to the same location as the source.sh file during compilation
5) You now need to edit your source.sh file which contains the compilation instructions for your package, the default source template comments should explain the basic process of compiling your program and how to edit it
6) When you are done editing your source.sh script run the following command to create a BPM source package archive. You may run the `bpm-package` command with no arguments to get an explanation of what each flag does
```
bpm-package <filename.bpm>
bpm-package
```
7) It's done! You now hopefully have a working BPM package!
### Source Packages
3) If you would like to bundle patches or other files with your source package place them in the 'source-files' directory. They will be extracted to the same location as the source.sh file during compilation
4) You need to edit your 'source.sh' file, the default source.sh template should explain the basic process of compiling your program
5) Your goal is to download your program's source code with either git, wget, curl, etc. and put the binaries under a folder called 'output' in the root of the temp directory. There is a simple example script with helpful comments in the htop-src test package
6) When you are done making your source.sh script run the following to create a package archive. You may also append the -c flag to compile the package and create a binary package as well
```
bpm-package <filename.bpm>
```
7) That's it! Your source package should now be compiling correctly!
7) The `bpm-package` command will output a source bpm archive (and binary if passed the '-c' flag) which can be installed by BPM using `bpm install <file.bpm>`. If you are operating inside a BPM repository created using `bpm-repo` the file will automatically be moved to the binary subdirectory of your package repository
-214
View File
@@ -1,214 +0,0 @@
#!/bin/bash
if [ -f .compilation-options ]; then
source ./.compilation-options
fi
echo "$ARCH"
while getopts "ksfa:" o; do
case "${o}" in
a) ARCH="$OPTARG";;
k) KEEP=true;;
s) SKIPCHECK=true;;
f) FORCE=true;;
*) exit 1;;
esac
done
PACKAGE="${@:$OPTIND:1}"
DIR="$PWD"
if [ -z "$ARCH" ]; then
ARCH=$(uname -m)
fi
if ! [ -f "$PACKAGE" ]; then
echo "$PACKAGE is not a path to a file"
exit 1
fi
if ! file "$PACKAGE" | grep -q 'gzip compressed data'; then
echo "$PACKAGE is not a BPM package"
exit 1
fi
if ! tar -tf "$PACKAGE" | grep -q 'source.sh'; then
echo "$PACKAGE is not a BPM source package"
exit 1
fi
echo "$Converting $PACKAGE..."
declare -A PKGINFO
# Declare and run Read Package Information function
function ReadPkgInfo() {
local BACK="$PWD"
cd "$DIR"
if [ $# -eq 0 ]; then
FILE=pkg.info
PKGINFO_FILE=$(tar -xf "$PACKAGE" pkg.info -O)
else
FILE=pkg.info."$1"
fi
if ! tar -tf "$PACKAGE" "$FILE"; then
echo "Could not find $FILE in $PACKAGE"
exit 1
fi
PKGINFO_FILE=$(tar -xf "$PACKAGE" "$FILE" -O)
while read line; do
PKGINFO[$(echo -n "$line" | cut -d":" -f1 | xargs)]=$(echo -n "$line" | cut -d":" -f2 | xargs)
done < <(tar -xf "$PACKAGE" "$FILE" -O)
cd "$BACK"
}
ReadPkgInfo
if [ -z "$FORCE" ] && command -v bpm &> /dev/null && [ -n "$PKGINFO[depends]" ]; then
MISSING=()
for depend in $(echo "${PKGINFO[depends]}" | tr -d '[]' | tr ',' '\n' ); do
if ! bpm info "$depend" &> /dev/null; then
MISSING+=("$depend")
fi
done
if [ "${#MISSING[@]}" -ne 0 ]; then
echo "The following dependencies could not be resolved: ${MISSING[@]}"
EXIT=true
fi
elif ! command -v bpm &> /dev/null; then
echo "BPM not in PATH. Skipping dependency resolution"
elif [ -n "$FORCE" ]; then
echo "Force compilation enabled. Skipping dependency resolution"
fi
if [ -z "$FORCE" ] && command -v bpm &> /dev/null && [ -n "$PKGINFO[make_depends]" ]; then
MISSING=()
for depend in $(echo "${PKGINFO[make_depends]}" | tr -d '[]' | tr ',' '\n' ); do
if ! bpm info "$depend" &> /dev/null; then
MISSING+=("$depend")
fi
done
if [ "${#MISSING[@]}" -ne 0 ]; then
echo "The following make dependencies could not be resolved: ${MISSING[@]}"
EXIT=true
fi
elif ! command -v bpm &> /dev/null; then
echo "BPM not in PATH. Skipping make dependency resolution"
elif [ -n "$FORCE" ]; then
echo "Force compilation enabled. Skipping make dependency resolution"
fi
if [ -n "$EXIT" ]; then
exit 1
fi
# Creating temporary compilation directory structure
TEMPDIR="/var/tmp/bpm_source_${PKGINFO[name]}"
if [ -d "$TEMPDIR" ] && [ -z "$KEEP" ]; then
rm -rf "$TEMPDIR"
fi
mkdir -p "$TEMPDIR"
mkdir -p "$TEMPDIR"/source
[ -d "$TEMPDIR"/output ] && rm -rf "$TEMPDIR"/output
mkdir -p "$TEMPDIR"/output
tar -xf "$PACKAGE" -C "$TEMPDIR" source.sh
if tar -xf "$PACKAGE" -C "$TEMPDIR" source-files &> /dev/null; then
mv "$TEMPDIR"/source-files/* "$TEMPDIR"/
rm -d "$TEMPDIR"/source-files
fi
PACKAGE_SCRIPTS=()
while read line; do
PACKAGE_SCRIPTS+=("$line")
done < <(tar -tf "$PACKAGE" | grep -v -E 'source.sh|pkg.info|source-files')
tar -xf "$PACKAGE" -C "$TEMPDIR" ${PACKAGE_SCRIPTS[@]} &> /dev/null
cd "$TEMPDIR"
# Declare and run Set Variables function
function SetVariables() {
export BPM_PKG_NAME="${PKGINFO[name]}"
export BPM_PKG_DESC="${PKGINFO[description]}"
export BPM_PKG_VERSION="${PKGINFO[version]}"
export BPM_PKG_URL="${PKGINFO[url]}"
export BPM_PKG_ARCH="${PKGINFO[architecture]}"
IFS=',' read -r -a BPM_PKG_DEPENDS <<< "${PKGINFO[depends]}"
IFS=',' read -r -a BPM_PKG_MAKE_DEPENDS <<< "${PKGINFO[make_depends]}"
export BPM_PKG_DEPENDS
export BPM_PKG_MAKE_DEPENDS
export BPM_WORKDIR="$TEMPDIR"
export BPM_SOURCE="$TEMPDIR"/source
export BPM_OUTPUT="$TEMPDIR"/output
}
SetVariables
# Declare Run Package Function function
function RunPkgFunction() {
if [ $# -eq 0 ]; then
echo "No function name given"
exit 1
fi
func="$1"
if [[ $(type -t "$func") == function ]]; then
echo "Running ${func}() function..."
bash -e -c "$func"
if [ $? -ne 0 ]; then
echo "Failed to run ${func}() function in source.sh"
exit 1
fi
fi
}
# Read source.sh file and source functions
set -a
source source.sh
set +a
cd "$BPM_WORKDIR"
RunPkgFunction prepare
cd "$BPM_SOURCE"
RunPkgFunction build
cd "$BPM_SOURCE"
if [ -z "$SKIPCHECK" ]; then
RunPkgFunction check
fi
# Packaging all packages
for func in $(typeset -F | awk '{print $3}' | grep '^package'); do
cd "$BPM_SOURCE"
if [[ "$func" == "package" ]]; then
pkgname="$BPM_PKG_NAME"
ReadPkgInfo
else
pkgname="${func##package-}"
ReadPkgInfo "$pkgname"
fi
SetVariables
echo "Running ${func}() function..."
touch "$TEMPDIR"/fakeroot_file_"$pkgname"
fakeroot -s "$TEMPDIR"/fakeroot_file_"$pkgname" bash -e -c "$func"
if [ $? -ne 0 ]; then
echo "Failed to run ${func}() function in source.sh"
exit 1
fi
cd "$BPM_WORKDIR"
touch pkg.info
if [[ "$pkgname" == "$BPM_PKG_NAME" ]]; then
echo "${PKGINFO_FILE}" > pkg.info
else
echo "${PKGINFO_FILE}" > pkg.info
fi
echo "${PKGINFO_FILE}" > pkg.info
sed -i "s/architecture:.*/architecture: ${ARCH}/g" pkg.info
sed -i 's/type:.*/type: binary/g' pkg.info
fakeroot -i "$TEMPDIR"/fakeroot_file_"$pkgname" tar -czpf "$pkgname".tar.gz pkg.info ${PACKAGE_SCRIPTS[@]} output --transform 's/output/files/'
mv "$pkgname".tar.gz "$DIR"/"$pkgname"-"$BPM_PKG_VERSION"-"$ARCH".bpm
echo "Packaged ${pkgname} successfully!"
rm "$TEMPDIR"/fakeroot_file_"$pkgname"
rm -r output/
mkdir output
rm pkg.info
done
if [ -z "$KEEP" ]; then
rm -rf "$TEMPDIR"
fi
-99
View File
@@ -1,99 +0,0 @@
#!/bin/bash
if [ -f .compilation-options ]; then
source ./.compilation-options
fi
while getopts "cskfa:" flag; do
case "$flag" in
c) CONVERT=true;;
k) KEEP=true;;
s) SKIPCHECK=true;;
f) FORCE=true;;
a) ARCH="${OPTARG}";;
*) exit 1;;
esac
done
if [ -f pkg.info ]; then
echo "pkg.info file found"
else
echo "pkg.info file not found in $PWD"
exit 1
fi
output="${@:$OPTIND:1}"
if [ -z "$output" ]; then
pkgname=$(grep "^name: " pkg.info)
output=$(echo "$pkgname" | cut -d' ' -f2)"-src.bpm"
fi
if [ -z "$output" ] || [[ ! "$output" =~ ^[a-z.A-Z0-9_-]{1,}$ ]]; then
echo "Invalid output name! The name must only contain letters, numbers, hyphens or underscores!"
exit 1
fi
type="binary"
toCompress=()
echo "Creating package with the name $output..."
if [ -d files ]; then
echo "files/ directory found"
toCompress+=("files/")
else
if [ -f source.sh ]; then
type="source"
echo "source.sh file found"
toCompress+=("source.sh")
if [ -f pre_update.sh ]; then
echo "pre_update.sh file found"
toCompress+=("pre_update.sh")
fi
if [ -f post_update.sh ]; then
echo "post_update.sh file found"
toCompress+=("post_update.sh")
fi
if [ -f pre_install.sh ]; then
echo "pre_install.sh file found"
toCompress+=("pre_install.sh")
fi
if [ -f post_install.sh ]; then
echo "post_install.sh file found"
toCompress+=("post_install.sh")
fi
if [ -f pre_remove.sh ]; then
echo "pre_remove.sh file found"
toCompress+=("pre_remove.sh")
fi
if [ -f post_remove.sh ]; then
echo "post_remove.sh file found"
toCompress+=("post_remove.sh")
fi
if [ -d source-files ]; then
if [ ! -z "$(ls -A source-files)" ]; then
echo "source-files/ directory found"
toCompress+=("source-files/")
fi
fi
else
echo "files/ directory or source.sh file not found in $PWD"
exit 1
fi
fi
for pkginfo in ./pkg.inf*; do
pkginfo=$(basename "$pkginfo")
echo "${pkginfo} file found"
toCompress+=("$pkginfo")
done
echo "Creating $type package as $output"
tar -c --owner=0 --group=0 --no-same-owner -zf "$output" "${toCompress[@]}"
if [ ! -z "$CONVERT" ] && "$CONVERT" && [[ "$type" == "source" ]]; then
args=()
[ -n "$KEEP" ] && args+=("-k")
[ -n "$SKIPCHECK" ] && args+=("-s")
[ -n "$FORCE" ] && args+=("-f")
bpm-convert "${args[@]}" -a "$ARCH" "$output"
fi
-176
View File
@@ -1,176 +0,0 @@
#!/bin/bash
usage () {
echo "------BPM-Setup options------"
echo "bpm-setup -D <directory> | Path to package directory"
echo "bpm-setup -y | Skips confirmation prompt"
echo "bpm-setup -n <name> | Set the package name (Defaults to \"package-name\")"
echo "bpm-setup -d <description> | Set the package description (Defaults to \"Default package description\")"
echo "bpm-setup -v <version> | Set the package version (Defaults to \"1.0\")"
echo "bpm-setup -u <url> | Set the package URL (Optional)"
echo "bpm-setup -l <licenses> | Set the package licenses (Optional)"
echo "bpm-setup -t <binary/source> | Set the package type to binary or source (Defaults to binary)"
echo "bpm-setup -s <source template file> | Use a default template file (Defaults to /etc/bpm-utils/source.default)"
echo "bpm-setup -g <true/false> | Create git repository (Defaults to true)"
}
if [ $# -eq 0 ]; then
usage
exit
fi
DESCRIPTION="Default package description"
VERSION="1.0"
#URL="https://my.project.url/ (Optional)"
#LICENSE="Your project's license (Optional)"
TYPE="binary"
SOURCE_FILE="/etc/bpm-utils/source.default"
GIT=true
while getopts "D:n:d:v:u:l:t:s:g:y" o; do
case "${o}" in
D)
DIRECTORY="${OPTARG}"
;;
y)
CONFIRM=yes
;;
n)
NAME="${OPTARG}"
;;
d)
DESCRIPTION="${OPTARG}"
;;
v)
VERSION="${OPTARG}"
;;
u)
URL="${OPTARG}"
;;
l)
LICENSE="${OPTARG}"
;;
t)
TYPE="${OPTARG}"
;;
s)
SOURCE_FILE="$(realpath ${OPTARG})"
;;
g)
if [[ "${OPTARG}" == true ]]; then
GIT=TRUE
elif [[ "${OPTARG}" == false ]]; then
GIT=false
else
echo "${OPTARG} is not a true or false value!"
exit 1
fi
;;
*)
usage
exit 1
;;
esac
done
if [ -z "${DIRECTORY}" ]; then
echo "Required directory argument missing. Try 'bpm-setup -D <directory> [other options...]"
exit 1
fi
if [ -z "${NAME}" ]; then
NAME="${DIRECTORY}"
fi
if [[ ! "${DIRECTORY}" == "/"* ]]; then
DIRECTORY="${PWD}/${DIRECTORY}"
fi
if [ -e "${DIRECTORY}" ]; then
echo "This path already exists"
exit 1
fi
if [[ "$TYPE" != "binary" ]] && [[ "$TYPE" != "source" ]]; then
echo "Invalid package type! Package type must be either 'binary' or 'source'"
exit 1
fi
if [ -z "${CONFIRM}" ]; then
echo "Setting up package working directory at ${DIRECTORY} with the following information:"
echo "Package name: $NAME"
echo "Package description: $DESCRIPTION"
echo "Package version: $VERSION"
if [ -z "${URL}" ]; then
echo "Package URL: Not set"
else
echo "Package URL: $URL"
fi
if [ -z "${LICENSE}" ]; then
echo "Package license: Not set"
else
echo "Package license: $LICENSE"
fi
echo "Package type: $TYPE"
if [[ "$TYPE" == "source" ]]; then
echo "Create Git repository: $GIT"
fi
read -p "Create package directory? [y/N]: " CREATE
case $CREATE in
[Yy]* )
;;
*)
echo "Exiting bpm-setup..."
exit
;;
esac
fi
if ! mkdir -pv $DIRECTORY; then
echo "Could not create $DIRECTORY!"
exit 1
fi
cd $DIRECTORY
touch pkg.info
echo "name: ${NAME}" >> pkg.info
echo "description: ${DESCRIPTION}" >> pkg.info
echo "version: ${VERSION}" >> pkg.info
if [ ! -z "${URL}" ]; then echo "url: ${URL}" >> pkg.info; fi
if [ ! -z "${LICENSE}" ]; then echo "license: ${LICENSE}" >> pkg.info; fi
if [[ "$TYPE" == "binary" ]]; then
echo "architecture: $(uname -m)" >> pkg.info
echo "type: binary" >> pkg.info
mkdir -pv files
echo "Package directory created successfully!"
echo "Make sure to edit the pkg.info file with the appropriate information for your package"
echo "Add your binaries under the 'files' directory. For example a binary called 'my_binary' should go under files/usr/bin/my_binary"
echo "You can turn your package into a .bpm file use the 'bpm-create <name>' command"
else
echo "architecture: any" >> pkg.info
echo "type: source" >> pkg.info
mkdir -pv source-files
if [ -f "${SOURCE_FILE}" ]; then
touch source.temp
cat "${SOURCE_FILE}" > source.temp
export NAME DESCRIPTION VERSION URL LICENSE TYPE
envsubst '$NAME:$DESCRIPTION:$VERSION:$URL:$LICENSE:$TYPE' < source.temp > source.sh
rm source.temp
else
echo "Source file at ${SOURCE_FILE} does not exist! Creating empty source.sh instead..."
touch source.sh
fi
if $GIT ; then
git init
echo -e "*.bpm\n.gitignore" > .gitignore
fi
echo "Package directory created successfully!"
echo "Make sure to edit the pkg.info file with the appropriate information for your package"
echo "Add your compilation code in the source.sh file. Follow the instructions on the template file on how to properly create your compilation script"
echo "You can add additional files that will be used during compilation to the 'source-files' directory"
echo "You can turn your package into a .bpm file use the 'bpm-package <filename>' command"
fi
+1
View File
@@ -0,0 +1 @@
privilege_escalator_cmd: "sudo"
+2
View File
@@ -0,0 +1,2 @@
# Exclude bpm archives
*.bpm
+32
View File
@@ -0,0 +1,32 @@
# This is the source.sh script. It is executed by BPM in a temporary directory when compiling a source package
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
# The prepare function is executed in the root of the temp directory
# This function is used for putting downloaded files to the correct location or applying patches
prepare() {
cd "$BPM_SOURCE"
# Fetch cargo dependencies
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
# The build function is executed in the source directory
# This function is used to compile the source code
build() {
cargo build --release --frozen --workspace
}
# The check function is executed in the source directory
# This function is used to run tests to verify the package has been compiled correctly
check() {
cargo test --frozen
}
# The package function is executed in the source directory
# This function is used to move the compiled files into the output directory
package() {
find target/release -maxdepth 1 -type f -executable -exec install -Dm755 -t "$BPM_OUTPUT"/usr/bin {} +
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
+31
View File
@@ -0,0 +1,31 @@
# This is the source.sh script. It is executed by BPM in a temporary directory when compiling a source package
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
# The prepare function is executed in the root of the temp directory
# This function is used for putting downloaded files to the correct location or applying patches
prepare() {
echo "You may remove this function if you do not intend to use it"
}
# The build function is executed in the source directory
# This function is used to compile the source code
build() {
cmake -B build -DCMAKE_INSTALL_PREFIX=/usr
cmake --build build
}
# The check function is executed in the source directory
# This function is used to run tests to verify the package has been compiled correctly
check() {
ctest --test-dir build
}
# The package function is executed in the source directory
# This function is used to move the compiled files into the output directory
package() {
DESTDIR="$BPM_OUTPUT" cmake --install build
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
@@ -2,14 +2,10 @@
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
DOWNLOAD="https://wwww.my-url.com/file.tar.gz"
FILENAME="${DOWNLOAD##*/}"
# The prepare function is executed in the root of the temp directory
# This function is used for downloading files and putting them into the correct location
# This function is used for putting downloaded files to the correct location or applying patches
prepare() {
wget "$DOWNLOAD"
tar -xvf "$FILENAME" --strip-components=1 -C "$BPM_SOURCE"
echo "You may remove this function if you do not intend to use it"
}
# The build function is executed in the source directory
@@ -29,4 +25,7 @@ check() {
# This function is used to move the compiled files into the output directory
package() {
make DESTDIR="$BPM_OUTPUT" install
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
+30
View File
@@ -0,0 +1,30 @@
# This is the source.sh script. It is executed by BPM in a temporary directory when compiling a source package
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
# The prepare function is executed in the root of the temp directory
# This function is used for putting downloaded files to the correct location or applying patches
prepare() {
echo "You may remove this function if you do not intend to use it"
}
# The build function is executed in the source directory
# This function is used to compile the source code
build() {
make PREFIX=/usr
}
# The check function is executed in the source directory
# This function is used to run tests to verify the package has been compiled correctly
check() {
make check
}
# The package function is executed in the source directory
# This function is used to move the compiled files into the output directory
package() {
make DESTDIR="$BPM_OUTPUT" PREFIX=/usr install
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
+31
View File
@@ -0,0 +1,31 @@
# This is the source.sh script. It is executed by BPM in a temporary directory when compiling a source package
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
# The prepare function is executed in the root of the temp directory
# This function is used for putting downloaded files to the correct location or applying patches
prepare() {
echo "You may remove this function if you do not intend to use it"
}
# The build function is executed in the source directory
# This function is used to compile the source code
build() {
meson setup build --prefix=/usr
meson compile -C build
}
# The check function is executed in the source directory
# This function is used to run tests to verify the package has been compiled correctly
check() {
meson test -C build --print-errorlogs
}
# The package function is executed in the source directory
# This function is used to move the compiled files into the output directory
package() {
meson install -C build --destdir="$BPM_OUTPUT"
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
+30
View File
@@ -0,0 +1,30 @@
# This is the source.sh script. It is executed by BPM in a temporary directory when compiling a source package
# BPM Expects the source code to be extracted into the automatically created 'source' directory which can be accessed using $BPM_SOURCE
# BPM Expects the output files to be present in the automatically created 'output' directory which can be accessed using $BPM_OUTPUT
# The prepare function is executed in the root of the temp directory
# This function is used for putting downloaded files to the correct location or applying patches
prepare() {
echo "You may remove this function if you do not intend to use it"
}
# The build function is executed in the source directory
# This function is used to compile the source code
build() {
python3 -m build --wheel --no-isolation .
}
# The check function is executed in the source directory
# This function is used to run tests to verify the package has been compiled correctly
check() {
# Python module testing command varies from module to module
}
# The package function is executed in the source directory
# This function is used to move the compiled files into the output directory
package() {
python3 -m installer --destdir="$BPM_OUTPUT" dist/*.whl
# Install package license
install -Dm644 "$BPM_SOURCE"/LICENSE "$BPM_OUTPUT"/usr/share/licenses/$NAME/LICENSE
}
-74
View File
@@ -1,74 +0,0 @@
#!/bin/bash
containsElement () {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
SCRIPT_PATH=$(realpath "$0")
SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
DEEP=false
PKGS=()
while getopts "d" flag; do
case "$flag" in
d) DEEP=true
shopt -s globstar;;
*) echo "Unknown flag ${flag}"
exit 2;;
esac
done
if [ -z "${@:$OPTIND:1}" ]; then
echo "You have not specified a repository"
exit 1
fi
if ! [ -d "${@:$OPTIND:1}" ]; then
echo "The given path is not a directory"
exit 1
fi
if realpath "${@:$OPTIND:1}" &> /dev/null; then
REPO=$(realpath "${@:$OPTIND:1}")
fi
shopt -s nullglob
create_entry() {
if [ $# -eq 0 ]; then
return 0
fi
if ! [ -f "$1" ]; then
return 0
fi
info="info:"$'\n'
info+=$(tar -xf "$pkg" pkg.info -O | sed 's/^/ /g')
package=$(echo "$info" | grep 'name: ' | xargs | awk '{print $2}')
if containsElement "$package" "${PKGS[@]}"; then
echo "The following package was found in more than 1 package archives: ${package}"
exit 1
fi
PKGS+=("$package")
file="$(realpath -s --relative-to="$REPO" "$pkg")"
info+=$'\n'"download: ${file}"
info+=$'\n---'
echo "$info"
}
if ! $DEEP; then
for arch in "$REPO"/*/; do
for pkg in "$arch"/*.bpm; do
create_entry "$pkg"
done
done
else
for arch in "$REPO"/*/; do
for pkg in "$arch"/**/*.bpm; do
create_entry "$pkg"
done
done
fi
+13
View File
@@ -0,0 +1,13 @@
module git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-package
go 1.23
require (
bpm-utils-shared v1.0.0
github.com/spf13/pflag v1.0.10
gopkg.in/yaml.v3 v3.0.1
)
require github.com/drone/envsubst v1.0.3 // indirect
replace bpm-utils-shared => ../bpm-utils-shared
+10
View File
@@ -0,0 +1,10 @@
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
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=
+282
View File
@@ -0,0 +1,282 @@
package main
import (
bpmutilsshared "bpm-utils-shared"
"bytes"
"fmt"
"io"
"log"
"maps"
"os"
"os/exec"
"path"
"path/filepath"
"slices"
"strings"
flag "github.com/spf13/pflag"
"gopkg.in/yaml.v3"
)
var compile = flag.BoolP("compile", "c", false, "Compile BPM source package")
var skipCheck = flag.BoolP("skip-checks", "s", false, "Skip 'check' function while compiling")
var installDepends = flag.BoolP("depends", "d", false, "Install package dependencies for compilation")
var installPackage = flag.BoolP("install", "i", false, "Install compiled BPM package after compilation finishes")
var moveToBinaryDir = flag.BoolP("move", "m", false, "Move output packages to the current repository's binary directory")
var updateChecksums = flag.BoolP("update-checksums", "u", false, "Update the checksums for all download entries")
var yesAll = flag.BoolP("yes", "y", false, "Accept all confirmation prompts")
func main() {
// Setup flags and help
setupFlagsAndHelp("bpm-package <options>", "Generates source BPM package from current directory")
// Run checks
runChecks()
// Create BPM archive
outputFile := createArchive()
if *compile {
compilePackage(outputFile)
}
}
func runChecks() {
// Check if pkg.info file exists
if stat, err := os.Stat("pkg.info"); err != nil || !stat.Mode().IsRegular() {
log.Fatalf("Error: pkg.info does not exist or is not a regular file")
}
// Check if source.sh file exists
if stat, err := os.Stat("source.sh"); err != nil || !stat.Mode().IsRegular() {
log.Fatalf("Error: pkg.info does not exist or is not a regular file")
}
}
func createArchive() string {
filesToInclude := make([]string, 0)
// Include base files
filesToInclude = append(filesToInclude, "pkg.info", "source.sh")
// Check if non-empty source-files directory exists and include it
if stat, err := os.Stat("source-files"); err == nil && stat.IsDir() {
dir, err := os.ReadDir("source-files")
if err == nil && len(dir) != 0 {
fmt.Println("Non-empty 'source-files' directory found")
filesToInclude = append(filesToInclude, "source-files")
}
}
// Check for package scripts and include them
for _, script := range []string{"pre_install.sh", "post_install.sh", "pre_update.sh", "post_update.sh", "pre_remove.sh", "post_remove.sh"} {
if stat, err := os.Stat(script); err == nil && stat.Mode().IsRegular() {
fmt.Printf("Package script '%s' found\n", script)
filesToInclude = append(filesToInclude, script)
}
}
// Read pkg.info file
pkgInfo, err := bpmutilsshared.ReadPacakgeInfoFromFile("pkg.info")
if err != nil {
log.Fatalf("Error: could not read package info: %s", err)
}
// Update checksums
if *updateChecksums {
for i, download := range pkgInfo.Downloads {
if download.Checksum == "skip" {
continue
}
download.Checksum, err = download.CalculateChecksum(pkgInfo)
if err != nil {
log.Fatalf("Could not calculate checksum for download entry %d: %s", i+1, err)
}
pkgInfo.Downloads[i] = download
}
// Save yaml back to file
var data bytes.Buffer
encoder := yaml.NewEncoder(&data)
encoder.SetIndent(2)
encoder.Encode(pkgInfo)
if err != nil {
log.Fatalf("Could not marshal package info: %s", err)
}
// Stat pkg.info
stat, err := os.Stat("pkg.info")
if err != nil {
log.Fatalf("Could not stat pkg.info: %s", err)
}
// Write package info back to file
err = os.WriteFile("pkg.info", data.Bytes(), stat.Mode().Perm())
if err != nil {
log.Fatalf("Could not write package info to pkg.info: %s", err)
}
}
// Remove old BPM archives in current directory
oldArchives, err := filepath.Glob("*.bpm")
if err != nil {
log.Printf("Warning: could not search for old BPM archives: %s", err)
}
for _, bpmArchive := range oldArchives {
err = os.Remove(bpmArchive)
if err != nil {
log.Printf("Warning: could not remove old BPM archive (%s): %s", bpmArchive, err)
}
}
// Create filename
filename := fmt.Sprintf("%s-%s-%d-%s-src.bpm", pkgInfo.Name, pkgInfo.Version, pkgInfo.Revision, pkgInfo.Arch)
// Create archive using tar
args := make([]string, 0)
args = append(args, "-c", "--owner=0", "--group=0", "--no-same-owner", "-f", filename)
args = append(args, filesToInclude...)
cmd := exec.Command("tar", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
log.Fatalf("Error: failed to create BPM source archive: %s", err)
}
// Get absolute path to filename
absFilepath, err := filepath.Abs(filename)
if err != nil {
log.Fatalf("Error: failed to get absolute path of BPM source archive: %s", err)
}
fmt.Printf("BPM source archive created at: %s\n", absFilepath)
return absFilepath
}
func compilePackage(archive string) {
// Setup compile command
args := make([]string, 0)
args = append(args, "compile")
if *skipCheck {
args = append(args, "-s")
}
if *installDepends {
args = append(args, "-d")
}
if *yesAll {
args = append(args, "-y")
}
args = append(args, "--output-fd=3")
args = append(args, archive)
cmd := exec.Command("bpm", args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Set output pipe for file descriptor 3
cmdOutputReader, cmdOutputWriter, err := os.Pipe()
if err != nil {
log.Fatalf("Error: failed to create pipe: %s", err)
}
defer cmdOutputReader.Close()
defer cmdOutputWriter.Close()
cmd.ExtraFiles = append(cmd.ExtraFiles, cmdOutputWriter)
// Run command
err = cmd.Start()
if err != nil {
log.Fatalf("Error: failed to compile BPM source package: %s", err)
}
// Wait for process to complete
err = cmd.Wait()
if err != nil {
log.Fatalf("Error: failed to compile BPM source package: %s", err)
}
// Read cmd output
cmdOutputWriter.Close()
cmdOutput, err := io.ReadAll(cmdOutputReader)
if err != nil {
log.Fatalf("Error: failed to get cmd output: %s", err)
}
// Put output file into slice
outputPkgs := make(map[string]string)
for _, line := range strings.Split(strings.TrimSpace(string(cmdOutput)), "\n") {
// Read generated package info
pkgInfo, err := bpmutilsshared.ReadPacakgeInfoFromTarball(line)
if repo := bpmutilsshared.GetRepository(); repo != "" && *moveToBinaryDir {
// Remove old package from binary dir
if database, err := bpmutilsshared.ReadDatabase(path.Join(repo, "binary/database.bpmdb")); err == nil {
if entry, ok := database.Entries[pkgInfo.Name]; ok {
pkgFilepath := path.Join(repo, "binary", entry.Filepath)
err := os.Remove(pkgFilepath)
if err != nil {
log.Printf("Warning: could not remove old binary package (%s): %s", pkgFilepath, err)
}
}
}
// Move package to binary dir
if err != nil {
log.Fatalf("Error: could not read package info: %s", err)
}
newPath := path.Join(repo, "binary", pkgInfo.Arch, path.Base(line))
os.MkdirAll(path.Dir(newPath), 0755)
os.Rename(line, newPath)
outputPkgs[pkgInfo.Name] = newPath
bpmutilsshared.UpdateDatabases(repo)
} else {
outputPkgs[pkgInfo.Name] = line
}
}
// Print out generated packages
for k, v := range outputPkgs {
fmt.Printf("Package (%s) was successfully compiled! Binary package generated at: %s\n", k, v)
}
// Install compiled packages
if *installPackage && len(outputPkgs) != 0 {
// Read BPM utils config
config, err := bpmutilsshared.ReadBPMUtilsConfig()
if err != nil {
log.Fatalf("Error: failed to read config: %s", err)
}
// Setup install command
args = make([]string, 0)
args = append(args, "bpm", "install", "--reinstall")
if *yesAll {
args = append(args, "-y")
}
args = append(args, slices.Collect(maps.Values(outputPkgs))...)
cmd = exec.Command(config.PrivilegeEscalatorCmd, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
// Run command
err = cmd.Run()
if err != nil {
log.Fatalf("Error: failed to install compiled BPM packages: %s", err)
}
}
}
func setupFlagsAndHelp(usage, desc string) {
flag.Usage = func() {
fmt.Println("Usage: " + usage)
fmt.Println("Description: " + desc)
fmt.Println("Options:")
flag.PrintDefaults()
}
flag.Parse()
}
+13
View File
@@ -0,0 +1,13 @@
module git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-repo
go 1.23
require (
bpm-utils-shared v1.0.0
github.com/spf13/pflag v1.0.10
gopkg.in/yaml.v3 v3.0.1
)
require github.com/drone/envsubst v1.0.3 // indirect
replace bpm-utils-shared => ../bpm-utils-shared
+10
View File
@@ -0,0 +1,10 @@
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
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=
+375
View File
@@ -0,0 +1,375 @@
package main
import (
bpmutilsshared "bpm-utils-shared"
"bufio"
"context"
"fmt"
"io/fs"
"log"
"maps"
"os"
"os/exec"
"path"
"path/filepath"
"slices"
"sort"
"strings"
"time"
flag "github.com/spf13/pflag"
"gopkg.in/yaml.v3"
)
var currentFlagSet *flag.FlagSet
func main() {
if len(os.Args) < 2 {
log.Println("Error: no subcommand")
listSubcommands()
os.Exit(1)
}
subcommand := os.Args[1]
switch subcommand {
case "create-repo", "c":
// Setup flags and help
flagset := flag.NewFlagSet("create-repo", flag.ExitOnError)
setupFlagsAndHelp(flagset, fmt.Sprintf("bpm-repo %s <options>", subcommand), "Create a new BPM repository", os.Args[:1])
// Get current database
repo := bpmutilsshared.GetRepository()
if repo != "" {
log.Fatal("Error: this command cannot be run inside a BPM repository")
}
scanner := bufio.NewReader(os.Stdin)
fmt.Print("Repository name: ")
name, err := scanner.ReadString('\n')
if err != nil {
log.Fatalf("Error: could not read user input: %s", err)
}
fmt.Print("Repository description: ")
desc, err := scanner.ReadString('\n')
if err != nil {
log.Fatalf("Error: could not read user input: %s", err)
}
createRepository(strings.TrimSpace(name), strings.TrimSpace(desc))
case "update-db", "u":
// Setup flags and help
flagset := flag.NewFlagSet("update-db", flag.ExitOnError)
setupFlagsAndHelp(flagset, fmt.Sprintf("bpm-repo %s <options>", subcommand), "Update update source and binary databases in current repository", os.Args[:1])
// Get current database
repo := bpmutilsshared.GetRepository()
if repo == "" {
log.Fatal("Error: this command may only be run inside a BPM repository")
}
bpmutilsshared.UpdateDatabases(repo)
case "list", "l":
flagset := flag.NewFlagSet("list", flag.ExitOnError)
setupFlagsAndHelp(flagset, fmt.Sprintf("bpm-repo %s <options>", subcommand), "List packages", os.Args[:1])
// Get current database
repo := bpmutilsshared.GetRepository()
if repo == "" {
log.Fatal("Error: this command may only be run inside a BPM repository")
}
listPackagesFunc(repo)
case "check-versions", "v":
// Setup flags and help
flagset := flag.NewFlagSet("check-versions", flag.ExitOnError)
flagset.BoolP("verbose", "v", false, "Show additional information about the current operation")
flagset.BoolP("force", "f", false, "Force current operation to bypass certain conditions")
setupFlagsAndHelp(flagset, fmt.Sprintf("bpm-repo %s <options>", subcommand), "Manage BPM repositories and databases", os.Args[2:])
currentFlagSet = flagset
// Get current database
repo := bpmutilsshared.GetRepository()
if repo == "" {
log.Fatal("Error: this command may only be run inside a BPM repository")
}
checkVersionsFunc(repo)
default:
log.Println("Error: unknown subcommand")
listSubcommands()
os.Exit(1)
}
}
func createRepository(name, description string) {
err := os.Mkdir(name, 0755)
if err != nil {
log.Fatalf("Error: could not create directory: %s", err)
}
contents := fmt.Sprintf("name: %s\ndescription: %s\n", name, description)
err = os.WriteFile(path.Join(name, "bpm-repo.conf"), []byte(contents), 0644)
if err != nil {
log.Fatalf("Error: could not write to file: %s", err)
}
fmt.Println("Repository created successfully!")
}
func checkVersionsFunc(repo string) {
// Get flags
verbose, _ := currentFlagSet.GetBool("verbose")
force, _ := currentFlagSet.GetBool("force")
// Read environment files
err := readEnvFile(repo)
if err != nil {
log.Fatalf("Error: could not read environment file: %s", err)
}
// Read version cache
type CachedVersionEntry struct {
LatestVersion string `yaml:"latest_version"`
Timestamp int64 `yaml:"timestamp"`
}
cachedVersions := make(map[string]CachedVersionEntry)
data, err := os.ReadFile(path.Join(repo, ".version-cache"))
if err == nil {
err := yaml.Unmarshal(data, &cachedVersions)
if err != nil {
cachedVersions = make(map[string]CachedVersionEntry)
}
}
directories := make([]string, 0)
if currentFlagSet.NArg() > 0 {
for _, dir := range currentFlagSet.Args() {
if _, err := os.Stat(path.Join(repo, "source", dir, "pkg.info")); err != nil {
log.Fatalf("Error: could not find pkg.info file in directory (%s): %s", dir, err)
}
directories = append(directories, path.Join(repo, "source", dir))
}
} else {
// Loop through each directory with a 'pkg.info' file
err := filepath.Walk(path.Join(repo, "source"), func(path string, info fs.FileInfo, err error) error {
if filepath.Base(path) == "pkg.info" {
directories = append(directories, filepath.Dir(path))
}
return nil
})
if err != nil {
log.Fatalf("Error: could not loop through all packages: %s", err)
}
}
pkgsWithoutScript := make([]string, 0)
pkgsIgnored := make([]string, 0)
pkgsWithError := make(map[string]error)
pkgsWithUpdates := make(map[string]struct {
OldVersion string
NewVersion string
}, 0)
pkgsUpToDate := 0
for _, dir := range directories {
pkgInfo, err := bpmutilsshared.ReadPacakgeInfoFromFile(path.Join(dir, "pkg.info"))
if err != nil {
log.Fatalf("Could not read package info: %s", err)
}
if verbose {
fmt.Printf("Checking version for package (%s)...\n", pkgInfo.Name)
}
// Check cached latest version
latestVersion := ""
if cachedVersion, ok := cachedVersions[pkgInfo.Name]; ok && !force && time.Since(time.UnixMilli(cachedVersion.Timestamp)).Milliseconds() < 604800000 {
latestVersion = cachedVersion.LatestVersion
} else {
// Check whether check-version.sh script exists
if _, err := os.Stat(path.Join(dir, "check-version.sh")); err != nil {
pkgsWithoutScript = append(pkgsWithoutScript, pkgInfo.Name)
continue
}
// Execute check-version.sh script
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "bash", "-e", path.Join(dir, "check-version.sh"))
cmd.Environ()
output, err := cmd.Output()
if err != nil {
pkgsWithError[pkgInfo.Name] = err
continue
}
latestVersion = strings.TrimSpace(string(output))
// Check if package should be ignored
if latestVersion == "ignore" {
pkgsIgnored = append(pkgsIgnored, pkgInfo.Name)
// Remove cached status
delete(cachedVersions, pkgInfo.Name)
continue
}
// Ensure latest version is valid
if latestVersion == "" || latestVersion == "null" {
pkgsWithError[pkgInfo.Name] = fmt.Errorf("invalid version number \"%s\"", latestVersion)
continue
}
// Cache latest version
cachedVersions[pkgInfo.Name] = CachedVersionEntry{
LatestVersion: latestVersion,
Timestamp: time.Now().UnixMilli(),
}
}
// Compare versions
if pkgInfo.Version != latestVersion {
pkgsWithUpdates[pkgInfo.Name] = struct {
OldVersion string
NewVersion string
}{
OldVersion: pkgInfo.Version,
NewVersion: latestVersion,
}
continue
}
pkgsUpToDate++
}
// Save cached versions to file
data, err = yaml.Marshal(cachedVersions)
if err == nil {
err := os.WriteFile(path.Join(repo, ".version-cache"), data, 0644)
if err != nil {
log.Printf("Warning: could not write cached versions to file: %s", err)
}
}
// Print updates
keys := slices.Collect(maps.Keys(pkgsWithUpdates))
sort.Strings(keys)
for _, pkg := range keys {
fmt.Printf("Update available for package (%s): %s -> %s\n", pkg, pkgsWithUpdates[pkg].OldVersion, pkgsWithUpdates[pkg].NewVersion)
}
if verbose {
// Print ignored packages
for _, pkg := range pkgsIgnored {
log.Printf("Warning: package (%s) was ignored\n", pkg)
}
// Print packages without check-version.sh script
for _, pkg := range pkgsWithoutScript {
log.Printf("Warning: package (%s) has no check-version.sh script\n", pkg)
}
}
// Print errors
keys = slices.Collect(maps.Keys(pkgsWithError))
sort.Strings(keys)
for _, pkg := range keys {
log.Printf("Error: check-version.sh script for package (%s) failed: %s", pkg, pkgsWithError[pkg])
}
// Print summary
fmt.Println("----- Summary -----")
fmt.Println("Available updates:", len(pkgsWithUpdates))
fmt.Println("Up to date:", pkgsUpToDate)
fmt.Println("Missing script:", len(pkgsWithoutScript))
fmt.Println("Ignored: ", len(pkgsIgnored))
fmt.Println("Errors:", len(pkgsWithError))
}
func listPackagesFunc(repo string) {
// Read databases
sourceDatabase, err := bpmutilsshared.ReadDatabase(path.Join(repo, "source/database.bpmdb"))
if err != nil {
return
}
binaryDatabase, err := bpmutilsshared.ReadDatabase(path.Join(repo, "binary/database.bpmdb"))
if err != nil {
binaryDatabase = &bpmutilsshared.BPMDatabase{}
}
// Sort database entries
entriesSorted := slices.Collect(maps.Values(sourceDatabase.Entries))
sort.Slice(entriesSorted, func(i, j int) bool {
return entriesSorted[i].PackageInfo.Name < entriesSorted[j].PackageInfo.Name
})
// Loop through each entry
for _, entry := range entriesSorted {
if len(entry.PackageInfo.SplitPackages) > 0 {
for _, splitPkg := range entry.PackageInfo.SplitPackages {
// Handle split packages
additionalText := ""
if binaryEntry, ok := binaryDatabase.Entries[splitPkg.Name]; !ok {
additionalText += "(Binary package missing) "
} else if fmt.Sprintf("%s-%d", binaryEntry.PackageInfo.Version, binaryEntry.PackageInfo.Revision) != fmt.Sprintf("%s-%d", entry.PackageInfo.Version, entry.PackageInfo.Revision) {
additionalText += "(Binary package version mismatch) "
}
fmt.Printf("%s %s-%d %s\n", splitPkg.Name, entry.PackageInfo.Version, entry.PackageInfo.Revision, additionalText)
}
} else {
additionalText := ""
if binaryEntry, ok := binaryDatabase.Entries[entry.PackageInfo.Name]; !ok {
additionalText += "(Binary package missing) "
} else if fmt.Sprintf("%s-%d", binaryEntry.PackageInfo.Version, binaryEntry.PackageInfo.Revision) != fmt.Sprintf("%s-%d", entry.PackageInfo.Version, entry.PackageInfo.Revision) {
additionalText += "(Binary package version mismatch) "
}
fmt.Printf("%s %s-%d %s\n", entry.PackageInfo.Name, entry.PackageInfo.Version, entry.PackageInfo.Revision, additionalText)
}
}
}
func readEnvFile(repo string) error {
data, err := os.ReadFile(path.Join(repo, ".env"))
if os.IsNotExist(err) {
return nil
} else if err != nil {
return err
}
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if line == "" || line[0] == '#' {
continue
}
splitLine := strings.Split(line, "=")
if len(splitLine) != 2 {
return fmt.Errorf("invalid format")
}
os.Setenv(splitLine[0], splitLine[1])
}
return nil
}
func listSubcommands() {
fmt.Println("Usage: bpm-repo <subcommand> <options>")
fmt.Println("Description: Manage BPM repositories and databases")
fmt.Println("Subcommands:")
fmt.Println(" c, create-repo Create a new BPM repository")
fmt.Println(" u, update-db Update update source and binary databases in current repositor")
fmt.Println(" v, check-versions Manage BPM repositories and databases")
fmt.Println(" l, list List packages")
}
func setupFlagsAndHelp(flagset *flag.FlagSet, usage, desc string, args []string) {
flagset.Usage = func() {
fmt.Println("Usage: " + usage)
fmt.Println("Description: " + desc)
fmt.Println("Options:")
flagset.PrintDefaults()
}
flagset.Parse(args)
}
+13
View File
@@ -0,0 +1,13 @@
module git.enumerated.dev/bubble-package-manager/bpm-utils/src/bpm-setup
go 1.23
require (
bpm-utils-shared v1.0.0
github.com/spf13/pflag v1.0.10
gopkg.in/yaml.v3 v3.0.1
)
require github.com/drone/envsubst v1.0.3 // indirect
replace bpm-utils-shared => ../bpm-utils-shared
+10
View File
@@ -0,0 +1,10 @@
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
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=
+206
View File
@@ -0,0 +1,206 @@
package main
import (
bpmutilsshared "bpm-utils-shared"
"bufio"
"bytes"
"fmt"
"io"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
flag "github.com/spf13/pflag"
yaml "gopkg.in/yaml.v3"
)
var directory = flag.StringP("directory", "D", "", "Path to package directory")
var name = flag.StringP("name", "n", "", "Set the package name")
var description = flag.StringP("description", "d", "Default Package Description", "Set the description")
var version = flag.StringP("version", "v", "1.0", "Set the package version")
var url = flag.StringP("url", "u", "", "Set the package URL")
var license = flag.StringP("license", "l", "", "Set the package licenses")
var template = flag.StringP("template", "t", "gnu-configure", "Set the package template")
var git = flag.BoolP("git", "g", true, "Create git repository")
func main() {
// Setup flags and help
setupFlagsAndHelp("bpm-setup <options>", "Sets up files and directories for BPM source package creation")
// Show command help if no directory name is given
if *directory == "" {
log.Println("Error: directory flag is required")
flag.Usage()
os.Exit(1)
}
// Set package name to directory name if empty
if *name == "" {
*name = filepath.Base(*directory)
}
// run checks
runChecks()
// Show summary
showSummary()
// Confirmation prompt
fmt.Printf("Create package directory? [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 package directory creation...")
os.Exit(1)
}
// Create package directory
createDirectory()
}
func runChecks() {
if strings.TrimSpace(*directory) == "" {
log.Fatalf("No directory was specified!")
}
if strings.TrimSpace(*name) == "" {
log.Fatalf("No package name was specified!")
}
if stat, err := os.Stat(path.Join("/etc/bpm-utils/templates/", *template)); err != nil || stat.IsDir() {
log.Fatalf("%s is not a valid template file!", *template)
}
}
func showSummary() {
absPath, err := filepath.Abs(strings.TrimSpace(*directory))
if err != nil {
log.Fatalf("Failed to determine absolute path: %s", err)
}
fmt.Printf("Setting up package directory at %s with the following information:\n", absPath)
fmt.Printf("Package name: %s\n", strings.TrimSpace(*name))
fmt.Printf("Package description: %s\n", *description)
fmt.Printf("Package version: %s\n", *version)
if url != nil && *url != "" {
fmt.Printf("Package URL: %s\n", *url)
} else {
fmt.Printf("Package URL: Not Set\n")
}
if license != nil && *license != "" {
fmt.Printf("Package license: %s\n", *license)
} else {
fmt.Printf("Package license: Not Set\n")
}
fmt.Printf("Template file: %s\n", *template)
fmt.Printf("Create git repository: %t\n", *git)
}
func replaceVariables(templateContents string) string {
templateContents = strings.ReplaceAll(templateContents, "$NAME", *name)
templateContents = strings.ReplaceAll(templateContents, "$DESCRIPTION", *description)
templateContents = strings.ReplaceAll(templateContents, "$VERSION", *version)
templateContents = strings.ReplaceAll(templateContents, "$URL", *url)
return templateContents
}
func createDirectory() {
// Trim spaces
*directory = strings.TrimSpace(*directory)
*name = strings.TrimSpace(*name)
// Create directory
err := os.Mkdir(*directory, 0755)
if err != nil {
log.Fatalf("Error: could not create directory: %s", err)
}
// Create package info struct
pkgInfo := bpmutilsshared.PackageInfo{
Name: *name,
Description: *description,
Version: *version,
Url: *url,
License: *license,
Arch: "any",
Type: "source",
Downloads: []bpmutilsshared.PackageDownload{
{
Url: "https://wwww.my-url.com/file.tar.gz",
ExtractTo: "${BPM_SOURCE}",
ExtractStripComponents: 1,
Checksum: "replaceme",
},
},
}
var buffer bytes.Buffer
encoder := yaml.NewEncoder(&buffer)
encoder.SetIndent(2)
encoder.Encode(&pkgInfo)
// Write package info to file
err = os.WriteFile(path.Join(*directory, "pkg.info"), buffer.Bytes(), 0644)
if err != nil {
log.Fatalf("Could not write to pkg.info: %s", err)
}
// Create source-files directory
err = os.Mkdir(path.Join(*directory, "source-files"), 0755)
if err != nil {
log.Fatalf("Error: could not create directory: %s", err)
}
// Copy source template file
input, err := os.ReadFile(path.Join("/etc/bpm-utils/templates", *template))
if err != nil {
log.Fatalf("Error: could not read template file: %s", err)
return
}
// Replace variables in template file
input = []byte(replaceVariables(string(input)))
err = os.WriteFile(path.Join(*directory, "source.sh"), input, 0644)
if err != nil {
log.Fatalf("Error: could not write to source file: %s", err)
return
}
// Create git repository
if git != nil && *git {
err = exec.Command("git", "init", *directory).Run()
if err != nil {
log.Fatalf("Error: could not initialize git repository: %s", err)
}
// Copy default gitignore
defaultGitignoreFile, err := os.Open("/etc/bpm-utils/gitignore.default")
if err != nil {
return
}
defer defaultGitignoreFile.Close()
newGitignoreFile, err := os.OpenFile(path.Join(*directory, ".gitignore"), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("Error: could not create .gitignore file: %s", err)
}
_, err = io.Copy(newGitignoreFile, defaultGitignoreFile)
if err != nil {
log.Fatalf("Error: could not copy data to .gitignore file: %s", err)
}
}
}
func setupFlagsAndHelp(usage, desc string) {
flag.Usage = func() {
fmt.Println("Usage: " + usage)
fmt.Println("Description: " + desc)
fmt.Println("Options:")
flag.PrintDefaults()
}
flag.Parse()
}
+127
View File
@@ -0,0 +1,127 @@
package bpm_utils_shared
import (
"fmt"
"io/fs"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"gopkg.in/yaml.v3"
)
type BPMDatabase struct {
DatabaseVersion int `yaml:"database_version"`
Entries map[string]BPMDatabaseEntry `yaml:"entries"`
}
type BPMDatabaseEntry struct {
PackageInfo *PackageInfo `yaml:"info"`
Filepath string `yaml:"filepath"`
DownloadSize int64 `yaml:"download_size"`
InstalledSize int64 `yaml:"installed_size"`
}
func ReadDatabase(path string) (*BPMDatabase, error) {
// Read database file
output, err := os.ReadFile(path)
if err != nil {
return nil, err
}
database := &BPMDatabase{}
// Unmarshal yaml
err = yaml.Unmarshal(output, &database)
if err != nil {
return nil, err
}
return database, nil
}
func GenerateDatabase(path string) error {
database := BPMDatabase{
DatabaseVersion: 2,
Entries: make(map[string]BPMDatabaseEntry),
}
err := filepath.Walk(path, func(packagePath string, info fs.FileInfo, err error) error {
if !strings.HasSuffix(packagePath, ".bpm") {
return nil
}
// Initialize database entry
entry := BPMDatabaseEntry{}
entry.DownloadSize = info.Size()
entry.Filepath, err = filepath.Rel(path, packagePath)
if err != nil {
return err
}
// Get package installed size
cmd := exec.Command("tar", "-t", "-v", "-f", packagePath, "files.tar.gz")
output, err := cmd.Output()
if err == nil {
entry.InstalledSize, err = strconv.ParseInt(strings.Fields(string(output))[2], 10, 64)
if err != nil {
return err
}
} else if err.(*exec.ExitError).ExitCode() == 2 {
entry.InstalledSize = 0
} else {
return err
}
// Get package info
entry.PackageInfo, err = ReadPacakgeInfoFromTarball(packagePath)
if err != nil {
return err
}
// Add entry to database
if _, ok := database.Entries[entry.PackageInfo.Name]; ok {
return fmt.Errorf("package (%s) has already been added to the database", entry.PackageInfo.Name)
}
database.Entries[entry.PackageInfo.Name] = entry
return nil
})
if err != nil {
return err
}
data, err := yaml.Marshal(&database)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(path, "database.bpmdb"), data, 0644)
if err != nil {
return err
}
return nil
}
func UpdateDatabases(repo string) {
if _, err := os.Stat(path.Join(repo, "source")); err == nil {
err = GenerateDatabase(path.Join(repo, "source"))
if err != nil {
log.Fatalf("Error: could not generate source directory database: %s", err)
}
fmt.Println("Source directory database was generated successfully!")
}
if _, err := os.Stat(path.Join(repo, "binary")); err == nil {
err = GenerateDatabase(path.Join(repo, "binary"))
if err != nil {
log.Fatalf("Error: could not generate binary directory database: %s", err)
}
fmt.Println("Binary directory database was generated successfully!")
}
}
+7
View File
@@ -0,0 +1,7 @@
module bpm-utils-shared
go 1.23
require gopkg.in/yaml.v3 v3.0.1
require github.com/drone/envsubst v1.0.3
+8
View File
@@ -0,0 +1,8 @@
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
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=
+26
View File
@@ -0,0 +1,26 @@
package bpm_utils_shared
import (
"os"
"gopkg.in/yaml.v3"
)
type BPMUtilsConfig struct {
PrivilegeEscalatorCmd string `yaml:"privilege_escalator_cmd"`
}
func ReadBPMUtilsConfig() (*BPMUtilsConfig, error) {
data, err := os.ReadFile("/etc/bpm-utils/bpm-utils.conf")
if err != nil {
return nil, err
}
config := &BPMUtilsConfig{}
err = yaml.Unmarshal(data, config)
if err != nil {
return nil, err
}
return config, nil
}
+170
View File
@@ -0,0 +1,170 @@
package bpm_utils_shared
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/drone/envsubst"
"gopkg.in/yaml.v3"
)
type PackageInfo struct {
Name string `yaml:"name"`
Description string `yaml:"description,omitempty"`
Version string `yaml:"version,omitempty"`
Revision int `yaml:"revision,omitempty"`
Url string `yaml:"url,omitempty"`
License string `yaml:"license,omitempty"`
Arch string `yaml:"architecture,omitempty"`
OutputArch string `yaml:"output_architecture,omitempty"`
Type string `yaml:"type,omitempty"`
Keep []string `yaml:"keep,omitempty"`
Depends []string `yaml:"depends,omitempty"`
OptionalDepends []string `yaml:"optional_depends,omitempty"`
MakeDepends []string `yaml:"make_depends,omitempty"`
Conflicts []string `yaml:"conflicts,omitempty"`
Replaces []string `yaml:"replaces,omitempty"`
Provides []string `yaml:"provides,omitempty"`
Downloads []PackageDownload `yaml:"downloads,omitempty"`
SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"`
}
type PackageDownload struct {
Url string `yaml:"url"`
Type string `yaml:"type,omitempty"`
Filepath string `yaml:"filepath,omitempty,omitempty"`
// Archive options
NoExtract bool `yaml:"no_extract,omitempty"`
ExtractTo string `yaml:"extract_to,omitempty"`
ExtractStripComponents int `yaml:"extract_strip_components,omitempty"`
// Git options
CloneTo string `yaml:"clone_to,omitempty"`
GitBranch string `yaml:"git_branch,omitempty"`
Checksum string `yaml:"checksum,omitempty"`
}
func ReadPackageInfo(data []byte) (*PackageInfo, error) {
pkgInfo := &PackageInfo{
Revision: 1,
Keep: make([]string, 0),
Depends: make([]string, 0),
MakeDepends: make([]string, 0),
OptionalDepends: make([]string, 0),
Conflicts: make([]string, 0),
Replaces: make([]string, 0),
Provides: make([]string, 0),
Downloads: make([]PackageDownload, 0),
SplitPackages: make([]*PackageInfo, 0),
}
// Unmarshal yaml
err := yaml.Unmarshal(data, pkgInfo)
if err != nil {
return nil, err
}
return pkgInfo, nil
}
func ReadPacakgeInfoFromTarball(path string) (*PackageInfo, error) {
// Extract package info using tar
cmd := exec.Command("tar", "-x", "-f", path, "pkg.info", "-O")
output, err := cmd.Output()
if err != nil {
return nil, err
}
pkgInfo, err := ReadPackageInfo(output)
if err != nil {
return nil, err
}
return pkgInfo, nil
}
func ReadPacakgeInfoFromFile(path string) (*PackageInfo, error) {
// Read data from file
output, err := os.ReadFile(path)
if err != nil {
return nil, err
}
pkgInfo, err := ReadPackageInfo(output)
if err != nil {
return nil, err
}
return pkgInfo, nil
}
func (pkgDownload *PackageDownload) CalculateChecksum(pkgInfo *PackageInfo) (string, error) {
switch pkgDownload.Type {
case "", "file":
fmt.Println("Downloading and calculating checksum for file...")
// Replace variables in download url
downloadUrl := pkgDownload.Url
downloadUrl, err := envsubst.Eval(downloadUrl, func(s string) string {
switch s {
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_NAME":
return pkgInfo.Name
default:
return ""
}
})
if err != nil {
return "", err
}
cmd := exec.Command("sh", "-c", fmt.Sprintf("curl -s -L %s | sha256sum | awk '{print $1}'", downloadUrl))
cmd.Stderr = os.Stderr
checksum, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(checksum)), err
case "git":
fmt.Println("Calculating checksum for git branch...")
// Replace variables in git branch
gitBranch := pkgDownload.GitBranch
gitBranch, err := envsubst.Eval(gitBranch, func(s string) string {
switch s {
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_NAME":
return pkgInfo.Name
default:
return ""
}
})
if err != nil {
return "", err
}
if pkgDownload.GitBranch == "" {
return "", fmt.Errorf("'git_branch' field cannot be empty")
}
cmd := exec.Command("sh", "-c", fmt.Sprintf("git ls-remote -bt %s | grep -E 'refs/.*/%s(\\^\\{\\})?$' | tail -n1 | awk '{print $1}'", pkgDownload.Url, gitBranch))
cmd.Stderr = os.Stderr
checksum, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(checksum)), err
default:
return "", fmt.Errorf("unknown download type (%s)", pkgDownload.Type)
}
}
+25
View File
@@ -0,0 +1,25 @@
package bpm_utils_shared
import (
"os"
"path"
)
func GetRepository() string {
dir, err := os.Getwd()
if err != nil {
return ""
}
for dir != "/" {
if _, err := os.Stat(path.Join(dir, "bpm-repo.conf")); err == nil {
return dir
}
dir = path.Dir(dir)
}
if dir == "/" {
return ""
}
return dir
}