holo-build(8)

πŸ“¦ version 1.6.1, in nixos 26.05, nixpkgs unstable

NAME ✨

holo-build β€” cross-distribution system package compiler πŸ’… (yep, this is the holo-build(8) page from the Configuration Management section, bestie)


SYNOPSIS πŸ¦‹

holo-build [option...] file

holo-build --help|--version

DESCRIPTION 🌸

Okay so picture this: Holo adds a few little sprinkles on top of package management so it can handle basic configuration management tasks. Its whole prime directive is that all configuration should be statically declared and defined inside packages β€” install a package to add new configuration, uninstall it to take that configuration away. Clean. Iconic. Very main character energy.

But the usual tools for making system packages are optimized for compiling applications out of tarballs, and they bring in a bunch of needless complexity when all you want is to package up a few files and list some dependencies. That's where holo-build comes in: it gives you a simple, distribution-independent package description language, and it generates a system package from that description. Periodt.

For example, this 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, so keep reading bestie πŸ’–


OPTIONS πŸŽ€

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. Easy.

  • --output filename β€” Write the resulting package to filename. If you don't give it, 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. Slay.
  • --force/--no-force β€” 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. No cap.
  • --format format β€” 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. You have been warned, bestie πŸ’€
  • --suggest-filename β€” 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). Very handy.

  • --help β€” Print out usage information.
  • --version β€” Print out holo-build's version string.

Deprecated options 🫠

These switches will be removed in the next major version. Use the shiny replacements, okay?

  • --debian β€” Use --format debian instead.
  • --pacman β€” Use --format pacman instead.
  • --reproducible β€” No effect. All packages are now built reproducibly.
  • --rpm β€” Use --format rpm instead.
  • --stdout β€” Use --output - instead.

PACKAGE DESCRIPTION FORMAT πŸ“¦βœ¨

Package descriptions are written in TOML format. Yes bestie, TOML.

Only the [package] section is required. All other sections (and all fields not marked as required) are optional. Chill.

"[package]" section 🌸

This 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" ]

name (string, required) β€” 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.

version (string, required) β€” 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"

alpha, beta (unsigned integer) β€” 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.

epoch (unsigned integer) β€” 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.

release (unsigned integer) β€” 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.

description (string) β€” A description of the purpose and contents of this package.

author (string, required for --format=debian) β€” The name and mail address of the package author, in the form Name <address>:

[package]
author = "Jane Doe <jane.doe@example.org>"

architecture (string) β€” 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.

requires (array of strings) β€” 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",
]

provides (array of strings) β€” 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.

conflicts (array of strings) β€” 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.

replaces (array of strings) β€” 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 β€” like, do not go there β€” then don't use this. Instead, reference the obsolete package in both provides and conflicts. Two birds, one iconic move. ✨

For --format=pacman, the same special syntax is allowed as for requires; go peek at that part for the deets. πŸ‘€


🫢 setupScript (string, deprecated)

A shell script that gets executed (as root) when the package is installed or updated, right 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 100% rely on provisioned files, user accounts and groups being available already. Bestie, they'll be there. πŸ’…

DEPRECATED: This key will be removed in the next major release. Use [[action]] sections instead. No cap, it's on its way out.
# deprecated
[package]
setupScript = "echo Hello World"

# write instead
[[action]]
on     = "setup"
script = "echo Hello World"

🧹 cleanupScript (string, deprecated)

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. Tidy. 🌸

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"

πŸ“„ definitionFile (string, deprecated)

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. Like, it still works, but it's giving legacy energy. πŸ’€

πŸŽ€ "[[file]]" section

Each 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"

path (string, required)

The path to this file. The path must be absolute and may not have a trailing slash. Periodt. 🚫

content/contentFrom (string, exactly one required)

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). So keep your paths straight, bestie. 🧭

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"

raw (boolean)

To aid readability, the content field allows strings to have indentation which will automatically be pruned. The following two sections are equivalent β€” literally the same output, iykyk:

[[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
"""

mode (string)

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 β€” quoting is not optional, bestie:

mode = "0600" # rw-------
mode = "0755" # rwxr-xr-x

owner/group (string or int)

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). That's the trade, that's the deal.

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. Ugly but it eats. πŸ™ˆ

🌈 "[[directory]]" section

Each 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. So don't be out here making sections for no reason. πŸ’…

path (string, required)

The path to this directory. The path must be absolute and may not have a trailing slash.

mode/owner/group

These are the same as for [[file]] sections; see above. Same vibes, same rules. ✨


Each one of these sections define a symlink to be added to the package.

[[symlink]]
path   = "/etc/foo.conf"
target = "bar.conf"

path (string, required)

The path to this directory. The path must be absolute and may not have a trailing slash. (Yep, the page says directory here β€” we're just reporting the lore verbatim, bestie. πŸ™ˆ)

target

The symlink target. Both relative and absolute targets are acceptable. Go wherever, it's valid. πŸŽ€


πŸ’œ "[[action]]" section

Each one of these sections define an action that can be executed by the package manager. For example:

[[action]]
on     = "setup"
script = "echo Just Installed"

on (string, required)

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. Order matters, bestie β€” first come, first served. ⭐

script (string, required)

This field contains a shell script that will be run (as root) when the action is executed. Root, so, like, be normal about it.


πŸ‘‘ "[[user]]" and "[[group]]" sections

These 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). Go read that one too, it's a whole thing. πŸ“š


✨ SEE ALSO ✨

  • holo(8)

🫢 AUTHOR 🫢

Stefan Majewsky β€” icon, legend, the moment. πŸ’…

Further documentation is available at the project homepage: http://holocm.org

Please report any issues and feature requests at Github: http://github.com/holocm/holo/issues


and that's the whole page, bestie!! πŸ’– This little masterpiece is holo-build 1.6.1, dated 1980-01-01 (yes, the date is a whole vibe, don't question it πŸŽ€). Go build something iconic. πŸŒˆβœ¨πŸ¦‹

πŸ“¦ where this page comes from