holo-build - cross-distribution system package compiler
holo-build [option...] file
holo-build --help|--version
Holo adds a few sprinkles on top of package management to make it suitable for basic configuration management tasks. Its prime directive is to have all configuration statically declared and defined within packages, which can be installed to add new configuration, or uninstalled to remove configuration from a system.
However, the tools for creating system packages are optimized towards compiling applications for tarballs, and introduce needless complexity when you just want to package up a few files and list some dependencies. holo-build provides a simple, distribution-independent package description language and generates a system package from such a description.
For example, the following package description will build a package that installs and configures systemd-timesyncd
with a custom NTP server:
[package]
name = "hologram-systemd-timesyncd"
version = "1.0"
author = "Jane Doe <jane.doe@example.org>"
requires = ["systemd"]
[[file]]
path = "/etc/systemd/timesyncd.conf.d/server.conf"
content = """
[Time]
NTP=ntp.someserver.local
"""
[[symlink]]
# as created by `systemctl enable systemd-timesyncd`
path = "/etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service"
target = "/usr/lib/systemd/system/systemd-timesyncd.service"
[[action]]
on = "setup"
script = "systemctl daemon-reload && systemctl start systemd-timesyncd"
[[action]]
on = "cleanup"
script = "systemctl stop systemd-timesyncd"
The package description format is described below.
The only positional argument file is the file name from where the package definition will be read. If no such argument is given, the package definition is read from standard input instead.
Write the resulting package to filename. If not given, write the package into the working directory using the naming convention for the corresponding output package format. If filename refers to a directory, write the package into this directory using the naming convention for the corresponding output package format. If the filename is -
, write the package to standard output.
By default, holo-build
will fail if the target file already exists. This behavior protects against the user forgetting to increase the version when editing the package description. With --force
, the target file will be overwritten when it exists.
This switch has no effect when --output -
or --suggest-filename
is in effect.
Generate a package of the specified format, instead of the default package format for the current distribution. Valid values are debian
, pacman
and rpm
.
WARNING: RPM generation is considered experimental because RPM is underdocumented and a bizarrely baroque format to begin with.
Do not generate a package. After reading and validating the package definition, just print on standard output the suggested filename for this package. The printed file name is the same one that will be used when --no-stdout
is in effect. The format for package filenames follows the recommendations of the targeted distribution:
$ cat input.toml
[package]
name = "foo"
version = "1.0"
$ holo-build --suggest-filename --format=debian < input.toml
foo_1.0-1_any.deb
$ holo-build --suggest-filename --format=pacman < input.toml
foo-1.0-1-any.pkg.tar.xz
This option can be used when auto-generating Makefiles, where the output filename needs to be known before holo-build
runs (for purposes of dependency resolution).
Print out usage information.
Print out holo-build's version string.
These switches will be removed in the next major version.
Use --format debian
instead.
Use --format pacman
instead.
No effect. All packages are now built reproducibly.
Use --format rpm
instead.
Use --output -
instead.
Package descriptions are written in the TOML format.
Only the [package]
section is required. All other sections (and all fields not marked as required) are optional.
[package]
sectionThis section is required and contains global properties for the package.
[package]
name = "example-package"
version = "1.2.5"
epoch = 2
release = 3
description = "An example package"
author = "Jane Doe <jane.doe@example.org>"
requires = [ "other-package >= 2.0" ]
provides = [ "example-package-api = 1.2" ]
conflicts = [ "bloatware-package" ]
replaces = [ "sample-package" ]
The package name. This string may never contain any slashes or newlines. The output format may impose additional charset or size restrictions on the package name. As a rule of thumb, the pattern [a-z0-9-]+
is a safe set of allowed package names.
For --format=debian
, the package name may contain [a-z0-9+-.]
, but the first character must be alphanumeric.
For --format=pacman
, the package name may contain [a-z0-9@._+-]
, but the first character may not be a hyphen.
The package version. To ensure sanity, holo-build enforces a relatively strict pattern. Version numbers must be numbers (0|[1-9][0-9]*
) chained together by dots:
version = "1.2.3"
version = "0.10"
version = "20151015"
If one of these is non-zero, the package is a prerelease version. For instance, the following corresponds to the full version 1.2.0-beta.3
:
version = "1.2.0"
beta = 3
The exact rendition of the version string depends on the output format.
An increase in the epoch (default: 0) can be used to force the package to be seen as newer than any previous version with a lower epoch. This is used when the version numbering scheme for a package changes, breaking normal version comparison logic.
The release number (default: 1) can be appended if the same package is rebuilt multiple times without its contents changing, in order for the built packages to be distinguishable from one another.
A description of the purpose and contents of this package.
--format=debian
)The name and mail address of the package author, in the form Name <address>
:
[package]
author = "Jane Doe <jane.doe@example.org>"
The target architecture of the package. The default (any
) is fine unless the package contains compiled binaries. Valid values include:
all any noarch
i386 i686
x86_64 amd64
arm armel armv5tl
armv6h armv6hl
armhf armv7h armv7hl
arm64 aarch64
The values on each line are synonymous to each other. The big variety of architecture strings comes from the big variety of supported target distributions, who all define architecture strings in their own way. holo-build will map the input architecture string to the one that's appropriate for the given target distribution.
A list of other packages that must be installed when this package is installed. Some package managers call these dependencies instead. When a specific package version is required, a version test can be appended, using one of the operators =
, >
, >=
, <
or <=
. If multiple version tests are required, the same package can be stated multiple times.
[package]
# require any version of foo, and a 2.x version of bar
requires = [ "foo", "bar >= 2.0", "bar < 3.0" ]
When the package contains any files below /usr/share/holo/$PLUGIN_ID
, a requirement
requires = [ "holo-$PLUGIN_ID" ]
is implied automatically.
For --format=pacman
only, a special syntax is allowed to require complete package groups (by giving the groupname with a group:
prefix), and to exclude certain packages or package groups from this group requirement (by prefixing the dependency with except:
). For example, to have the package require all packages from the xorg
group, except for the xorg-drivers
group and the xorg-docs
package:
[package]
requires = [
# require all packages from the xorg group
"group:xorg",
# except for xorg-docs and all packages in the xorg-drivers group
"except:xorg-docs",
"except:group:xorg-drivers",
]
A list of other packages (or virtual packages) that the software provides the features of. This means that this package can satisfy another package's requirement for the provided package. If a specific version is provided, this can be specified with the same syntax as for requires
.
[package]
name = "rewrite-of-foo"
provides = [ "foo = 2.1" ] # this package acts like foo-2.1
For --format=debian
, the provided version syntax is not allowed. provides
may only contain plain package names.
A list of other packages that may not be installed when this package is installed. Version tests can be added using the same syntax as for requires
.
For --format=pacman
, the same special syntax is allowed as for requires
; see there for details.
A list of obsolete packages that this package replaces. If this package is not installed, but one of the obsolete packages is installed, a system upgrade will result in the obsolete package being uninstalled, and this package being installed as a replacement.
If this package can replace the obsolete package, but it shall not be replaced automatically, don't use this; reference the obsolete package in both provides
and conflicts
instead.
For --format=pacman
, the same special syntax is allowed as for requires
; see there for details.
A shell script that will be executed (as root) when the package is installed or updated, after the package files have been extracted to the file system.
If the package contains any files below /usr/share/holo
, then holo apply
will automatically run before this setup script is executed, so you can rely on provisioned files, user accounts and groups to be available already.
DEPRECATED: This key will be removed in the next major release. Use [[action]]
sections instead.
# deprecated
[package]
setupScript = "echo Hello World"
# write instead
[[action]]
on = "setup"
script = "echo Hello World"
A shell script that will be executed (as root) when the package is being uninstalled, after the package files have been removed from the file system.
If the package contains any files below /usr/share/holo
, then holo apply
will automatically run before this cleanup script is executed.
DEPRECATED: This key will be removed in the next major release. Use [[action]]
sections instead.
# deprecated
[package]
cleanupScript = "echo Hello World"
# write instead
[[action]]
on = "cleanup"
script = "echo Hello World"
A path where [[user]]
and [[group]]
sections will be placed inside the package. See the description of [[user]]
and [[group]]
sections below. When omitted, the default value is /usr/share/holo/users-groups/${package_name}.toml
.
DEPRECATED: This key can be specified for compatibility reasons, but its use is discouraged, and it will be removed in the next major release.
[[file]]
sectionEach one of these sections define a file to be added to the package.
[[file]]
path = "/etc/foo.conf"
mode = "0600"
owner = "foouser"
group = "foogroup"
# alternative 1
content = """
content for foo.conf
content for foo.conf
"""
# alternative 2
contentFrom = "input.txt"
The path to this file. The path must be absolute and may not have a trailing slash.
If the content
field is given, it contains the content of this file. Alternatively, contentFrom
may reference a file whose contents will be used. This file must be present at package-build time; relative paths will be interpreted relative to the directory of the input file (or, if the package definition is presented on standard input, to the current working directory of the holo-build
process).
If content
is given, it may not be empty. To create an empty file, you can use /dev/null
as a source:
[[file]]
path = "/etc/empty-file.conf"
contentFrom = "/dev/null"
To aid readability, the content
field allows strings to have indentation which will automatically be pruned. The following two sections are equivalent:
[[file]]
path = "/etc/example.conf"
content = """
foo
bar
baz
"""
[[file]]
path = "/etc/example.conf"
content = """
foo
bar
baz
"""
To disable this behavior and use leading whitespace verbatim, set the raw
flag:
[[file]]
path = "/etc/example.conf"
raw = true
content = """
foo
bar
baz
"""
The mode bits for this file. Since TOML does not support octal number literals, this field must be given as a string containing an octal number:
mode = "0600" # rw-------
mode = "0755" # rwxr-xr-x
The owner (or group) for this file. If this field contains an integer, it is interpreted as the ID of the user or group. If this field contains a string, it is interpreted as a user/group name.
Since user/group names cannot be mapped to IDs at package build time, specifying a name will result in the file being packaged as belonging to user/group root
, and the actual user/group will be applied at install time using chown(1) or chgrp(1).
BUG: For --format=rpm
, specification of numeric IDs is broken. When encountering a numeric user/group ID in a holo-build package, RPM will say things like
user 42 does not exist - using root
group 42 does not exist - using root
and fall back to UID/GID 0. As a workaround, call chown(1) or chgrp(1) from the package's setup script to fix the file ownership.
[[directory]]
sectionEach one of these sections define a directory to be added to the package.
[[directory]]
path = "/var/lib/foo"
mode = "0600"
owner = "foouser"
group = "foogroup"
Note that directories are usually created automatically when files are placed in them. A [[directory]]
section is only required to include an empty directory in the package, or to assign non-standard permissions or specific ownership to a directory.
The path to this directory. The path must be absolute and may not have a trailing slash.
These are the same as for [[file]]
sections; see above.
[[symlink]]
sectionEach one of these sections define a symlink to be added to the package.
[[symlink]]
path = "/etc/foo.conf"
target = "bar.conf"
The path to this directory. The path must be absolute and may not have a trailing slash.
The symlink target. Both relative and absolute targets are acceptable.
[[action]]
sectionEach one of these sections define an action that can be executed by the package manager. For example:
[[action]]
on = "setup"
script = "echo Just Installed"
This field defines when this action will be executed. The following values are valid:
on = "setup" # run right after package is installed or upgraded
on = "cleanup" # run right after package is removed
If there are multiple actions with the same on
value, they will be executed in the order in which they are given in the package description.
This field contains a shell script that will be run (as root) when the action is executed.
[[user]]
and [[group]]
sectionsThese can be used to provision user accounts and groups when the package is installed. For example:
[package]
name = "foobar"
version = "1.0"
[[group]]
name = "foobargroup"
system = true
[[user]]
name = "foobaruser"
uid = 285
group = "foobargroup"
The user and group definitions are validated at package compilation time, and written into /usr/share/holo/users-groups/${package_name}.toml
. Because of this file, a dependency on holo-users-groups
is implied and holo apply
is executed in the setup and cleanup scripts. So the previous example is functionally equivalent to:
[package]
name = "foobar"
version = "1.0"
depends = ["holo-users-groups"]
[[file]]
path = "/usr/share/holo/users-groups/foobar.toml"
content = """
[[group]]
name = "foobargroup"
system = true
[[user]]
name = "foobaruser"
uid = 285
group = "foobargroup"
"""
[[action]]
on = "setup"
script = "holo apply"
[[action]]
on = "cleanup"
script = "holo apply"
The actual syntax and semantics of [[user]]
and [[group]]
sections is described in holo-users-groups(8).
Stefan Majewsky
Further documentation is available at the project homepage: https://holocm.org
Please report any issues and feature requests at GitHub: https://github.com/holocm/holo-build/issues