Installation
If you're using Arch Linux you can get pkger from AUR repositories with your favourite package manager like so:
paru pkger-rs
On other Linux distributions or MacOS download one of the latest prebuild static binaries from
here. If your desired target is not on the list you'll have to build
pkger from source by cloning the repository from https://github.com/vv9k/pkger
and building it with:
cargo build --release
Configuration
By default pkger will look for the config file named .pkger.yml
in the config directory appropriate for the OS
that pkger is run on. If there is no global configuration, current directory will be scanned for the same file.
To specify the location of the config file use --config
or -c
parameter.
The configuration file has a following structure:
# required
recipes_dir: ""
output_dir: ""
# optional
log_dir: ""
images_dir: ""
runtime_uri: "unix:///var/run/docker.sock"
# Disable colored output globally
no_color: true
ssh:
# this will make the ssh auth socket available to the container so that it can use private keys from the host.
forward_agent: true
# This will allow tools that use SSH to connect to hosts that are not present in the `known_hosts` file
disable_key_verification: true
# override default images used by pkger
custom_simple_images:
deb: ubuntu:latest
rpm: centos:latest
# To define custom images add the following
images:
- name: rocky
target: rpm
- name: debian
target: deb
# if pkger fails to find out the operating system you can specify it by os parameter
- name: arch
target: pkg
os: Arch Linux
The required fields when running a build are recipes_dir
and output_dir
. First tells pkger where to look for
recipes to build, the second is the directory where the final packages will end up.
When using custom images their location can be specified with images_dir
.
If container runtime daemon that pkger should connect does not run on a default unix socket override the uri with runtime_uri
parameter. pkger will automatically determine wether the provided runtime uri is a Podman or Docker daemon.
If an option is available as both configuration parameter and cli argument pkger will favour the arguments passed during startup.
Generate configuration file and directories
To quickly start of with pkger use the pkger init
subcommand that will create necessary directories and the
configuration file. Default locations can be overridden by command line parameters.
Recipes
Each recipe is a directory containing at least a recipe.yml
or recipe.yaml
file located at recipes_dir
specified
in the configuration.
The recipe is divided into 2 required (metadata, build) and 3 optional (config, install, env) parts. To read more on each topic select a subsection in the menu.
Here's an example working recipe for pkger:
metadata:
name: pkger
description: pkger
arch: x86_64
license: MIT
version: 0.1.0
maintainer: "vv9k"
url: "https://github.com/vv9k/pkger"
git: "https://github.com/vv9k/pkger.git"
provides: [ pkger ]
depends:
pkger-deb: [ libssl-dev ]
pkger-rpm: [ openssl-devel ]
build_depends:
all: [ gcc, pkg-config ]
pkger-deb: [ curl libssl-dev ]
pkger-rpm: [ cargo ]
pkger-pkg: [ cargo ]
env:
RUSTUP_URL: https://sh.rustup.rs
configure:
steps:
- cmd: curl -o /tmp/install_rust.sh $RUSTUP_URL
deb: true
- cmd: sh /tmp/install_rust.sh -y --default-toolchain stable
deb: true
build:
steps:
- cmd: cargo build --color=never
rpm: true
pkg: true
- cmd: $HOME/.cargo/bin/cargo build --color=never
deb: true
install:
steps:
- cmd: dir -p usr/bin
- cmd: install -m755 $PKGER_BLD_DIR/target/debug/pkger usr/bin/
You can declare a new recipe with a subcommand. It will automatically create a directory in recipes_dir
containing a recipe.yml
with the generated YAML recipe:
$ pkger new recipe [OPTIONS] <NAME>
There is also a way to remove recipes. The remove
subcommand erases the whole directory of a recipe if
such exists:
$ pkger remove recipes <NAMES>...
# or shorhand
# or shorhand 'rm' for 'remove' and 'rcp' for 'recipes'
$ pkger rm rcp <NAMES>...
To see existing recipes use:
$ pkger list recipes
# or shorhand 'ls' for 'list' and 'rcp' for 'recipes'
$ pkger ls rcp <NAMES>...
# for more detailed output
$ pkger list -v recipes
Metadata (Required)
Contains all fields that describe the package being built.
required fields
metadata:
name: pkger
description: pkger
license: MIT
version: 0.1.0
optional fields
To specify which images a recipe should use add images parameter with a list of image targets. This field is ignored
when building with --simple
flag.
images: [ rocky, debian ]
You can also specify that all images apply to this recipe with:
all_images: true
sources
This fields are responsible for fetching the files used for the build. When both git
and source
are specified
pkger will fetch both to the build directory.
If source
starts with a prefix like http
or https
the file that if points to will be downloaded. If the file is an
archive like .tar.gz
or .tar.xz
or .zip
it will be directly extracted to
$PKGER_BLD_DIR
, otherwise the file will be copied to the directory untouched.
source: "" # remote source or file system location
# can also specify multiple sources:
source:
- 'http://some.website.com/file.tar.gz'
- some_dir # relative path will be prefixed with recipe directory
- /some/absolute/path # can be a directory or a file
git: https://github.com/vv9k/pkger.git # will default to branch = "master"
# or specify a branch like this:
git:
url: https://github.com/vv9k/pkger.git
branch: dev
Environment variables are available for this fields so this is possible:
source: "https://github.com/vv9k/${RECIPE}/${RECIPE_VERSION}"
common
Optional fields shared across all targets.
release: "1" # defaults to "0"
epoch: "42"
maintainer: "vv9k"
# The website of the package being built
url: https://github.com/vv9k/pkger
arch: x86_64 # defaults to `noarch` on RPM and `all` on DEB, `x86_64` automatically converted to `amd64` on DEB...
skip_default_deps: true # skip installing default dependencies, it might break the builds
exclude: ["share", "info"] # directories to exclude from final package
group: "" # acts as Group in RPM or Section in DEB build
dependencies
Common fields that specify dependencies, conflicts and provides will be added to the spec of the final package.
This fields can be specified as arrays:
depends: []
conflicts: []
provides: []
Or specified per image as a map below.
pkger will install all dependencies listed in build_depends
, choosing an appropriate package manager for each
supported distribution. Default dependencies like gzip
or git
might be installed depending on the target job type.
build_depends:
# common dependencies shared across all images
all: ["gcc", "pkg-config", "git"]
# dependencies for custom images
rocky: ["cargo", "openssl-devel"]
debian: ["curl", "libssl-dev"]
To specify same dependencies for multiple images join the images by +
sign like this:
rocky+fedora34: [ cargo, openssl-devel ]
ubuntu20+debian: [ libssl-dev ]
# you can later specify dependencies just for this images
debian: [ curl ]
if running a simple build and there is a need to specify dependencies for the target add dependencies for one of this images:
pkger-rpm+pkger-apk+pkger-pkg: ["cargo"]
pkger-deb: ["curl"]
pkger-gzip: []
A custom image, for example rocky
, will also use dependecies defined for pkger-rpm
. The same will apply for all rpm based images (or images that have their target specified to RPM in the configuration)
Patches
To apply patches to the fetched source code specify them just like dependencies. Patches can be specified as just file
name in which case pkger will look for the patch in the recipe directory, if the path is absolute it will be read
directly from the file system and finally if the patch starts with an http
or https
prefix the patch will be fetched
from remote source.
patches:
- some-local.patch
- /some/absolute/path/to.patch
- https://someremotesource.com/other.patch
- patch: with-strip-level.patch
images: [ debian ] # specify the images that this patch should be aplied on
strip: 2 # this specifies the number of directories to strip before applying the patch (known as -pN or --stripN option in UNIX patch tool
RPM fields
Optional fields that will be used when building RPM target.
rpm:
vendor: ""
icon: ""
summary: "shorter description" # if not provided defaults to value of `description`
config_noreplace: "%{_sysconfdir}/%{name}/%{name}.conf"
pre_script: ""
post_script: ""
preun_script: ""
postun_script: ""
# Disable automatic dependency processing. Setting this to true has no effect.
auto_req_prov: false
# acts the same as other dependencies - can be passed as array
#obsoletes: ["foo"]
# or as a map
obsoletes:
rocky: ["foo"]
DEB fields
Optional fields that may be used when building a DEB package.
deb:
priority: ""
built_using: ""
essential: true
# specify the content of post install script
postinst: ""
# same as all other dependencies but deb specific
pre_depends: []
recommends: []
suggests: []
breaks: []
replaces: []
enhances: []
PKG fields
Optional fields that will be used when building a PKG package.
pkg:
# location of the script in `$PKGER_OUT_DIR` that contains pre/post install/upgrade/remove functions
# to be included in the final pkg
install: ".install"
# A list of files to be backed up when package will be removed or upgraded
backup: ["/etc/pkger.conf"]
# A list of packages that this package replaces
replaces: []
# This are dependencies that this package needs to offer full functionality.
optdepends:
# Each dependency should contain a short description in this format:
- "libpng: PNG images support"
- "alsa-lib: sound support"
APK fields
Optional fields that will be used when building a APK package.
apk:
install: "$pkgname.pre-install $pkgname.post-install"
# A list of packages that this package replaces
replaces: []
# A list of dependencies for the check phase
checkdepends: []
# If not provided a new generated key will be used to
# sign the package
private_key: "/location/of/apk_signing_key"
Scripts
pkger has 3 defined build phases - configure, build and install of which only build is required to create a package.
Each phase has field called steps
that takes an array of steps to execute during a given phase. A step can be a simple
string that will be executed in the default shell like "echo 123"
or an entry that specifies on what targets it should
be executed like:
- cmd: >-
echo only on deb targets
deb: true
To set a working directory during the script phase set the working_dir
parameter like so:
working_dir: /tmp
Environment variables are available for this field so this is possible:
working_dir: ${PKGER_BLD_DIR}/${RECIPE}-${RECIPE_VERSION}-${SOME_USER_DEFINED_VAR}
To use a different shell to execute each command set the shell
parameter:
shell: "/bin/bash" # optionally change default `/bin/sh`
configure (Optional)
Optional configuration steps. If provided the steps will be executed before the build phase.
The working directory will be set to $PKGER_BLD_DIR
configure:
shell: "/bin/bash"
steps:
- cmd: >-
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
build (Required)
This is the phase where the package should be assembled/compiled/linked and so on. All steps executed during the build
will have the working directory seto to $PKGER_BLD_DIR
. This directory will contain either
extracted sources if source
is specified in metadata or a git repository if git
was specified.
build:
steps:
- cmd: $HOME/.cargo/bin/cargo build --release .
- images: [ debian ]
cmd: echo 'hello from Debian' # will only be executed on image `debian`
- cmd: echo 'will only run on images with target == `rpm`'
rpm: true
# same applies to other targets
pkg: false
apk: false
deb: false
gzip: falze
- cmd: echo 'only on version 0.2.0'
versions: [ 0.2.0 ]
# fields can also be combined
- cmd: echo 'only on debian version 0.2.0'
versions: [ 0.2.0 ]
images: [ debian ]
]
install (Optional)
Optional steps that (if provided) will be executed after the build phase. Working directory of each step will be set to
$PKGER_OUT_DIR
so you can use relative paths with commands like install. Each file that
ends up in $PKGER_OUT_DIR
will be available in the final package unless explicitly
excluded by exclude
field in metadata. So in the example below, the file that is
installed will be available as /usr/bin/pkger
with permissions preserved.
install:
steps:
- cmd: >-
install -m755 $PKGER_BLD_DIR/target/release/pkger usr/bin/pkger
env (Optional)
Optional environment variables that should be available during the scripts phase and in some metadata fields.
env:
HTTPS_PROXY: http://proxy.domain.com:1234
RUST_LOG: trace
pkger variables
Some variables will be available to use during the build like:
$PKGER_OS
the distribution of current container$PKGER_OS_VERSION
version of the distribution if applies$PKGER_BLD_DIR
the build directory with fetched source or git repo in the container$PKGER_OUT_DIR
the final directory from which pkger will copy files to target package$RECIPE
the name of the recipe that is built$RECIPE_VERSION
the version of the recipe$RECIPE_RELEASE
the release of the recipe
Recipes inheritance
Recipes support inheriting fields from a defined base recipe to avoid repetition. For example here is a definition of a base package:
---
metadata:
name: base-package
version: 0.1.0
description: pkger base package testing recipe inheritance
arch: x86_64
license: MIT
images: [ rocky, debian ]
build:
working_dir: $PKGER_OUT_DIR
steps:
- cmd: echo 123 >> ${RECIPE}_${RECIPE_VERSION}
And here is a child recipe using from
field to define the parent recipe:
---
from: base-package
metadata:
name: child-package1
version: 0.2.0
description: pkger child package testing recipe inheritance from base-package
install:
shell: /bin/bash
steps:
- cmd: >-
if [[ $(cat ${RECIPE}_${RECIPE_VERSION}) =~ 123 ]]; then exit 0; else
echo "Test file ${RECIPE}_${RECIPE_VERSION} has invalid content"; exit 1; fi
The child-package1
will share the build
steps as well as arch
, license
, images
fields. After merging the child recipe will look something like this:
---
from: base-package
metadata:
name: child-package1
version: 0.2.0
description: pkger child package testing recipe inheritance from base-package
arch: x86_64
license: MIT
images: [ rocky, debian ]
build:
working_dir: $PKGER_OUT_DIR
steps:
- cmd: echo 123 >> ${RECIPE}_${RECIPE_VERSION}
install:
shell: /bin/bash
steps:
- cmd: >-
if [[ $(cat ${RECIPE}_${RECIPE_VERSION}) =~ 123 ]]; then exit 0; else
echo "Test file ${RECIPE}_${RECIPE_VERSION} has invalid content"; exit 1; fi
When defining a child recipe only from
and metadata.name
fields are required. Here is a minimal child recipe:
---
from: base-package
metadata:
name: child-package2
For a working example refer to the example
directory of pkger source tree.
Images
Images are an optional feature of pkger.By default pkger will create necessary images to build simple targets, they are completely distinct from user defined ones. User images offer higher customisation when it comes to preparing the build environment.
In the images directory specified by the configuration pkger will treat each subdirectory
containing a Dockerfile
as an image. The name of the directory will become the name of the image.
So example structure like this:
images
├── arch
│ └── Dockerfile
├── rocky
│ └── Dockerfile
└── debian
└── Dockerfile
pkger will detect 3 images - arch, rocky and debian.
Images with dependencies installed will be cached for each recipe-target combo to reduce the number of times the dependencies have to be pulled from remote sources. This saves a lot of space, time and bandwith.
You can declare a new image with a subcommand. It will automatically create a directory in images_dir
containing an empty Dockerfile
.
$ pkger new image <NAME>
There is also a way to remove images. The remove
subcommand erases the whole directory of an image if
such exists:
$ pkger remove images <NAMES>...
# or shorhand 'rm' for 'remove' and 'img' for 'images'
$ pkger rm img <NAMES>...
To see existing images use:
$ pkger list images
# or shorhand 'ls' for 'list' and 'img' for 'images'
$ pkger ls img
# for more detailed output
$ pkger list -v images
Build a package
Currently available targets are: rpm, deb, pkg, apk, gzip.
Simple build
To build a simple package using pkger use:
pkger build --simple [TARGETS] -- [RECIPES]
When using a simple build following linux distributions will be used for build images:
- rpm:
rockylinux/rockylinux:latest
- deb:
debian:latest
- pkg:
archlinux
- apk:
alpine:latest
- gzip:
debian:latest
To override the default images set custom_simple_images
like this:
custom_simple_images:
deb: ubuntu:18
rpm: fedora:latest
Custom images build
To use custom images drop the --simple
parameter and just use:
pkger build [RECIPES]
For this to have any effect the recipes have to have image targets defined (more on that here)
Examples
Build a recipe for all supported images:
pkger build recipe
# or shorthand 'b' for 'build'
pkger b recipe
Build all recipes for all supported images
pkger build --all
Build multiple recipes on specified custom images:
pkger build -i custom-image1 custom-image2 -- recipe1 recipe2
Build simple RPM, DEB, PKG... packages:
pkger build -s rpm -s deb -s pkg -s gzip -- recipe1
Build only RPM package:
pkger build -s rpm -- recipe1
Output
After successfully building a package pkger will put the output artifact to output_dir
specified in
configuration joined by the image name that was used to build the package.
Each image will have a separate directory with all of its output packages.
Signing
To sign packages automatically using a GPG key add the following to your configuration file:
gpg_key: /absolute/path/to/the/private/key
gpg_name: Packager Name # must be the same as the `Name` field on the key
When pkger detects the gpg key in the configuration it will prompt for a password to the key on each run.
Currently, only deb and rpm targets support signing.
Formatting output
By default pkger will display basic output as hierhical log with level set to INFO
. All log messages will be printed to stdout unless a --log-dir
flag (or log_dir
is specified in configuration) is provided, in that case there will be a single global log file in the logging directory created on each run as well as a separate file for each task.
To debug run with -d
or --debug
option. To surpress all output except for errors and warnings add -q
or --quiet
. To enable very verbose output add -t
or `--trace option.
Generate recipes
To generate a recipe declaratively from CLI use the pkger new recipe
subcommand. By default it requires only the name
of the package and creates a directory with recipe.yml
in it.
Create images
To create images use pkger new image <name>
. This will create a directory with a Dockerfile
in the images_dir
specified in the configuration.
Edit recipes, images and config
pkger provides utility subcommand edit
that invokes the default editor defined by $EDITOR
environment variable.
To make this functionality work, export this variable in your shell's init script like ~/.bashrc
.
Edit images and recipes by name:
# This will open up the Dockerfile in the `rocky` image.
$ pkger edit image rocky
# This will open up the `recipe.yml` or `recipe.yaml` file in `pkger-simple` recipe directory
$ pkger edit recipe pkger-simple
To edit the configuration file run:
$ pkger edit config
# or shorhand 'e' for 'edit'
$ pkger e <object>
Shell completions
pkger provides a subcommand to print shell completions. Supported shells are: bash, zsh, fish, powershell, elvish.
To print the completions run:
pkger print-completions bash
replacing bash
with whatever shell you prefer.
To have completions automatically add something along those lines to your .bashrc
, .zshrc
...:
. <(pkger print-completions bash)