_ _
| |_ ___| |_ _
| | . | | | |
|_|_|___|_|_ |
|___|
git mirror - github.com/owenewans/holy - branch master
file llm.txt
holy.conf.5 source=holy version=development sha256=e4a579af5be8f10bddb18a78414b1aaaacf3a8b6ed5198cb5d19ccede91b7706
HOLY.CONF(5) File Formats HOLY.CONF(5)
NAME
holy.conf - Holy source configuration format
STATUS
This page describes the syntax checker and source identity registration. A
registered holy-http source can mirror one explicitly pinned or explicitly
accepted unsigned current index through holypkg sync. A registered holy-git
source uses a pinned commit and index digest. A source with an Ed25519 pub-
lic key verifies signed generations. General multi-source resolution and
full disk installation are not implemented; the restricted local data/sta-
tic-ELF transaction uses separate commands.
SYNOPSIS
holypkg config check FILE
DESCRIPTION
Input must be UTF-8. Each record contains a key and whitespace-separated
arguments. Double quotes retain spaces. A hash sign at the start of a token
begins a comment outside quotes. Escapes are backslash-quote, double-back-
slash, backslash-n, backslash-t, backslash-r and backslash-x followed by
two hexadecimal digits. NUL is rejected. The parser does not run shell ex-
pansion.
Sections are [general], [resolver], [source NAME], [rule NAME], [install]
and [disk]. [install-plan], [disk-plan] and [disk-finalize-plan] are emit-
ted by holyinstall and are not user config sections. Include takes one
path relative to the containing file. Repeated scalar keys within a section
are errors with both source locations. The source repo and resolver prefer
and install artifact keys are lists and can repeat. Recursive includes are
rejected. The checker validates lexical and structural syntax; it does not
yet validate rule references. It checks that source parents exist and do
not form cycles. It rejects userinfo in URL authorities without echoing
credentials in diagnostics. It validates key names, argument counts, and
the scripts, trust, ask-sources and prefer values. The alias local is re-
served and cannot name a source.
Each populated source section requires type and at least one url or repo.
Repository names within one source must be distinct. Each populated rule
requires consumer, require and provider. Empty values are rejected. These
checks do not prove that a repository is reachable or its packages can re-
solve.
For holy-http and holy-git, "public-key PATH" names an Ed25519 PEM public
key. The path is relative to the file containing that entry. source plan
reads it and freezes the raw 32-byte public key in the reviewed registry
plan; source apply does not reopen the PEM file. "public-key-ed25519 HEX"
supplies those bytes as 64 lowercase hex digits. Only one form is allowed.
trust require needs a key for either native source type. A configured key
requires signed catalogs for sync, binding and later source queries even
when trust is warn. Private signing keys are not configuration inputs.
[install] accepts one root directory and ordered artifact SHA-256 entries
for holyinstall's pre-mounted-root package transaction. The first artifact
is the explicit package. The installer checks required fields, digest syn-
tax and duplicate artifacts before planning. See holyinstall(8). An ad-
dressable accept-arch list can approve placement of selected artifacts with
a different target architecture. The frozen plan retains each approval. An
accept-privileged list confirms the exact artifact hashes whose setuid exe-
cutables have been reviewed. holyinstall records these decisions in its
frozen plan and passes them to holypkg. The list key "source ARTI-
FACT_SHA256 SOURCE_ID" associates a selected local artifact with an active
registered origin. The installed instance retains the source ID and alias
snapshot; metadata inside the archive grants no source authority.
[disk] accepts one image path and layout gpt-ext4. The disk plan accepts
regular image files, not block devices. Its fixed plan schema forbids in-
clude. The installer's text menu can save [disk] alongside [install] and
prepare the disk plan independently of package selection. See holyin-
stall(8).
EXAMPLE
[general]
arch x86_64
scripts ask
[source arch]
type pacman
repo core "https://mirror.example/arch/$repo/os/$arch"
FILES
/etc/holy.conf is the proposed system path. The checker takes an explicit
file.
SOURCE IDENTITIES
The source plan command reads this syntax, including includes, and proposes
a registry replacement. The source apply command consumes that frozen plan;
it does not reread the configuration. See holypkg(8) for the command se-
quence.
The current source ID is SHA-256 of a canonical, sorted representation of
type, url and repo records. Alias, parent, family, priority, trust policy
and public key are excluded. Renaming an alias preserves the ID. Changing
an endpoint, backend type or repository name creates another ID and retains
the previous definition as inactive history. Readding the same definition
reactivates its existing ID. Equivalent URL spellings and mirrors are not
automatically identified as one source.
Registration requires endpoints with a scheme followed by :// and rejects
URL userinfo, queries, fragments and whitespace. Plans therefore cannot
carry those credential forms. Two aliases with the same definition require
a decision. Registration neither contacts a repository nor verifies its
signatures. Set installation can bind local artifacts to registered IDs
through explicit --source associations. The registry stores parent as a
source-id, separate from the endpoint-derived identity. Renaming a parent
alias retains the relationship. Parent preference applies when add discov-
ers a missing native provider; it does not replace the child's URL with the
parent's URL.
Optional family NAME groups sources for dependency preference. Optional
priority SIGNED_INTEGER ranks candidates within the same preference group;
a larger number wins. The default priority is zero. Family and priority are
policy, not part of source identity. A changed policy is recorded by source
apply. holypkg source show ALIAS displays the policy after apply.
A registered APK source uses type apk and one or more repo NAME HTTPS_BASE/
entries. holypkg apk sync names the repo, obtains its APKINDEX.tar.gz and
writes a source-id-bearing catalog and target-root binding. public-key
points to an RSA PEM public key for APK sources; source plan freezes its
SHA-256 fingerprint. trust require needs that key and a valid APKINDEX
signature. The matching key file is supplied to apk sync and apk fetch
through --public-key. Fetch verifies the bound APK package signature with
the same registered key. See holypkg(8) for the digest confirmation and
fetch sequence.
A registered APT source uses type apt and one url HTTPS_BASE/. public-key
points to a binary OpenPGP keyring; source plan freezes its SHA-256. trust
require needs that key. holypkg apt sync-source requires the same keyring
and records the stable source-id in its signed catalog and database bind-
ing. Queries and fetches can select it with source, suite, component and
index architecture; they recheck the current registry. The keyring is se-
lected by the user; adding a source does not import public keys from the
network. The --inrelease option selects a clearsigned InRelease; without
it sync-source uses Release and Release.gpg.
A registered RPM-MD source uses type rpm-md and one url HTTPS_BASE/, with
no repo entries. The base is the repository root that serves repodata/re-
pomd.xml. holypkg sync RPM_MD_SOURCE takes an explicit repomd --sha256 pin
and records the source-id in the catalog it writes. No repository signature
is checked, so trust require is not available for this backend. Query, ca-
pability lookup and fetch select the source by alias and recheck the regis-
tered url and source-id against the stored catalog.
A registered XBPS source uses type xbps and one url HTTPS_BASE/. public-key
points to an RSA PEM public key; source plan freezes the SHA-256 finger-
print of its public-key encoding. trust require needs that key. xbps sync-
source checks the selected key against the registry and the index metadata.
It also requires an explicit repodata SHA-256 pin. Source-aware queries
check the catalog's source-id, URL and recorded key fingerprint. Fetch re-
quires the same key file and verifies the package .sig2 signature. sync-
source also binds the catalog's conversion digest under the target data-
base; later queries can select that binding by source and index architec-
ture.
Holy September 2026 HOLY.CONF(5)
holygetiso.8 source=holy version=development sha256=ce2b437d781e75a9085de2dc946c063c161232d47a077ba02d4e3fdfa7fed88b
HOLYGETISO(8) System Administration HOLYGETISO(8)
NAME
holygetiso - build and boot-test a Holy image from explicit inputs
SYNOPSIS
holygetiso [--check] CONFIG
holygetiso --export-inputs IMAGE_OUTPUT DIRECTORY
DESCRIPTION
The current command drives the repository's bootstrap-image builder. Run it
from the Holy source directory after make. It accepts local or pinned-
source core inputs and pinned native source packages for additional appli-
cations. It rejects unknown sections, keys, duplicate scalar keys, missing
input paths and malformed quoting. It uses the holy.conf lexer; configura-
tion text is not executed by a shell. A top-level "include PATH" reads an-
other config relative to the containing file. Include cycles and duplicate
scalar keys across files fail.
The builder packages the core inputs, asks holyinstall and holypkg to in-
stall them into a separate root, generates the documentation bundle and
initramfs, creates an ISO, then runs the QEMU boot contract. The output di-
rectory must not exist. A successful exit requires a boot-tested image; the
build log, install preview, frozen plan, package hashes and VM report re-
main there. The command writes one expanded effective config with resolved
input paths. Its SHA-256 is included in build.record before the boot plan
is hashed. The builder copies those exact bytes into output/inputs/im-
age.conf and rejects a change between validation and copying. With
--check, the command validates the config and existing input paths and
prints the effective config hash, target architecture, profile and output
path without starting a build.
--export-inputs copies the inputs, normalized packages, sealed native mir-
rors, build record and frozen plans from an existing build output into a
new directory. It checks the input lock and source/install/boot plan
hashes before copying. SHA256SUMS records each copied file and is checked
before the command succeeds. The bundle preserves the exact package inputs
for inspection or a repeated experiment. host-tools.jsonl names each direct
executable used by the builder, its canonical path and SHA-256; the build
record binds that file. The bundle does not include the host compiler,
QEMU, firmware, their dependency closure or the Holy source checkout. The
destination must not exist.
CONFIGURATION
Paths are resolved relative to the config file. Existing input paths must
resolve at startup. The output path names a new directory; the ISO is writ-
ten as holy-ARCH.iso inside it. Supported sections and keys follow.
[image]
Required: arch (i686 or x86_64), output, kernel-version, limine-dir,
static-holypkg, static-holyinstall, static-cc, profile (static-core
or dual-libc), root-storage (ram, ext4 or gpt-ext4). Choose one
kernel-image local path or kernel-package ALIAS:PACKAGE from an ac-
tive pinned source. The source package must be named linux, match
kernel-version and target architecture, use libc nolibc, and contain
boot/vmlinuz. The builder preserves that .holy and source ID in the
install plan, and checks the extracted x86 kernel header before
building the image. make bootstrap-kernel can create that
linux.holy from a selected local x86 kernel image and an optional
modules staging tree. Its result enters an image through an explic-
itly configured source catalog. If the package contains
usr/lib/modules/KERNEL_VERSION/kernel/drivers/net/dummy.ko and its
modules.dep entry, the builder packages a static module probe. QEMU
requires the guest to load dummy and find it in /proc/modules on
each boot. Without that module the report marks kernel-module-load
untested. Optional: glibc-cc, musl-cc, libc-boot-state, network-re-
covery, install-test and install-firmware. Their supported combina-
tions are documented in holy-image(7). boot-test accepts required
(default) or build-only. The dual-libc profile requires musl-cc.
[packages]
Required: busybox, dinit and mdevd. The dual-libc profile also re-
quires glibc and musl. An installer test requires doas and storage-
tools. Each value is a local .holy path or a pinned source reference
where supported. The builder records original and normalized arti-
fact hashes separately. Repeated "add PATH" entries install addi-
tional local .holy packages in the same frozen set plan. Their orig-
inal archives remain in output/inputs and the build record identi-
fies each hash. An "add ALIAS:PACKAGE" entry fetches a named pack-
age from a pinned native source. The builder resolves requirements
against all configured sealed catalogs in a temporary root, then
copies selected .holy artifacts into inputs and binds each source ID
in holyinstall's frozen set plan. A unique exact provider can come
from another source. Other explicit "add ALIAS:PACKAGE" entries be-
come candidates for the current package; unresolved ambiguity stops
the build. The busybox, dinit, mdevd, glibc and musl fields accept
local artifacts or ALIAS:PACKAGE from a pinned source. The builder
records the source ID and original digest, then normalizes each core
artifact into a new unsigned Holy package. On an x86_64 builder tar-
geting i686, the resolver's x86 placement decision is recorded for
each selected x86 artifact. This does not claim that the host can
execute the i686 image. Doas and storage-tools fields require local
artifacts. Added packages must target the image architecture or
noarch; x86 packages are also accepted in an x86_64 image. Duplicate
package names fail before plan generation. The package manager
checks payload ownership and dependencies.
make source-ready-core accepts OUTPUT, SOURCE_ALIAS, SOURCE_URL and the
five *_PACKAGE input paths. It copies the bootstrap archives, rebuilds
their manifests with root ownership inside a user namespace, and writes a
sealed unsigned native repository plus core-source.conf for inclusion in an
image config. build.record links each new artifact digest to its original
digest. SOURCE_URL is provenance for this local repository; the command
does not publish files at that URL. This step requires user namespaces and
does not sign the resulting artifacts.
[resolver]
Optional "answers PATH" selects a holy-answers-1 file with source
decisions keyed by consumer artifact hash and requirement ID. "an-
swers-sha256 DIGEST" is required with it. holygetiso checks the di-
gest during config validation, then the builder copies the file to
output/inputs/resolver-answers and checks the digest again before
using it. The build record and exported input lock include that
copy. An answer naming a source that does not offer the exact re-
quirement fails in holypkg; a changed consumer hash cannot reuse an
old answer. An unresolved choice stops the build with status 3.
[source NAME]
Each used native source requires type holy-http or holy-git, "url"
and "index-sha256" with the exact lowercase digest of the selected
generation. holy-http uses an HTTPS directory URL ending in slash.
holy-git also requires "commit" with a full lowercase Git object ID.
The builder records the commit in build.record and checks it when
fetching or importing a mirror. "ca-file" names an optional local
CA file. "mirror PATH" selects an existing sealed snapshot for an
offline build. Its source ID, URL and index must match the config-
ured source and pin. The builder copies and revalidates it under
output/mirrors. "embed-mirror yes" also includes the entire sealed
catalog in the image root at /var/cache/holypkg/image-mirrors/NAME.
The source binding uses that root-relative location so fetch can
find it after the image boots. The default is no: only selected
packages enter the image cache, and the user syncs the source after
boot to obtain its current catalog. mirror and ca-file cannot be
combined. "trust require" with "public-key PATH" verifies an
Ed25519-signed catalog. holygetiso resolves the PEM path relative
to its containing config file and freezes the raw public key in the
effective config and source plan. Changing the key bytes changes the
effective config hash. A signed offline mirror must carry a valid
signature for that key. The key is checked again when a bound mirror
is queried. The image config uses the holy.conf lexer but index-
sha256 is specific to holygetiso. The builder registers all listed
sources and keeps the verified mirrors in output/mirrors. Its record
binds source IDs and index digests. An index digest is a pin, not
publisher signature evidence.
[docs]
Exactly one "include installed-man-pages" and "output
/usr/share/holy/llm.txt" are required. holypkg docs reads the in-
stalled root and generates this file from its selected man pages.
STATUS
The command returns 0 after a boot-tested build, 2 for invalid config, 6
for missing inputs or host requirements, and 1 for other failures. The out-
put build.record says "result incomplete" when a build fails after the out-
put directory is created. If QEMU is missing, or boot-test is build-only,
the builder writes "result untested" after producing the image and returns
6. A successful process exit alone does not certify hardware, a desktop or
all packages named in the Holy specification.
LIMITS
This bootstrap command supports holy-http and holy-git sources. For addi-
tional packages it uses holypkg's native resolver in a temporary root with
every pinned mirror bound. The resolver can select a unique exact provider
from another source. Explicitly listed packages from other sources become
candidate providers; unresolved ambiguity stops the build. The builder
copies only selected artifacts and their source IDs into the final install
plan. Boot core packages may come from a pinned native source. Full pack-
age policy and a portable export of build tools remain open. The command
stays in-tree while the source checkout provides build tools and profiles.
SEE ALSO
holy-image(7), holypkg(8), holyinstall(8), holy.conf(5)
Holy September 2026 HOLYGETISO(8)
holy-image.7 source=holy version=development sha256=cb82d96f230ddf9a651274a721ddf2bfbf5737808ecb10aaa69c8a002d5843f9
HOLY-IMAGE(7) System Overview HOLY-IMAGE(7)
NAME
holy-image - image build and libc recovery boot tests
BUILD
make bootstrap-kernel packages an existing x86 kernel image as linux.holy.
KERNEL_IMAGE, KERNEL_VERSION, ARCH and OUTPUT select its inputs and output;
MODULES_DIR optionally names a staging tree for that release. The builder
checks the x86 boot header, matches every .ko to modules.dep, verifies the
package and scans each module's architecture and vermagic. It records the
input and artifact hashes. This target packages an existing kernel; QEMU
boot acceptance belongs to holygetiso.
make bootstrap-image builds a live ISO and runs its QEMU boot test. ARCH
defaults to x86_64. ARCH=i686 supports BIOS optical ISOs with ROOT_STOR-
AGE=ram or ext4 and an independently bootable BIOS GPT disk with ROOT_STOR-
AGE=gpt-ext4. IMAGE_BOOT_TEST=build-only produces the image without a QEMU
run, records "result untested" and returns 6. If the selected QEMU binary
is missing, the builder takes the same path. Neither case counts as a re-
lease boot gate. The i686 ISO boots through El Torito; it is not a hybrid
USB image. The i686 profile requires an i686 kernel and matching static
BusyBox, dinit, mdevd, holypkg and C compiler inputs. The builder checks
package and executable architectures before installation. The dual-libc
profile also requires matching i686 glibc and musl runtime packages and C
compilers. The GPT profile has no i686 UEFI loader; its tested boot path
is BIOS. IMAGE_PROFILE defaults to dual-libc; static-core explicitly se-
lects the earlier profile without dynamic libraries. LIBC_BOOT_STATE is
present by default. For dual-libc, glibc, musl or both remove those run-
time payloads and their loader links before creating initramfs, while re-
taining installed records and cached LIBC_BOOT_STATE=remove-both requires
an ext4 or gpt-ext4 dual-libc root. The first guest boot runs both dynamic
probes, then holypkg removes both runtime packages with an explicit broken-
dependency decision and reboots. Static BusyBox, dinit and holypkg run on
the second boot, report broken providers, install the same two cached .holy
artifacts through reviewed plans and repeat the probes. The QEMU report re-
quires both boot contracts and an unchanged read-only base image.
ROOT_STORAGE defaults to ram. ext4 selects a persistent recovery test for
dual-libc and requires mke2fs and qemu-img. The kernel must include ext4
and virtio-blk, and the BusyBox package must include switch_root, sync and
reboot. gpt-ext4 selects an independently bootable GPT disk with the same
ext4 recovery contract. It additionally requires sfdisk, mkfs.fat and
mtools; xorriso is only required for ISO profiles. NETWORK_RECOVERY de-
faults to off. The explicit fixture value requires a dual-libc RAM image
with LIBC_BOOT_STATE=both. The builder omits both libc archives from the
guest cache, retains copies for a local HTTPS fixture, and packages a gen-
erated test CA. The guest assigns the fixture address with static BusyBox
ip, downloads both archives with static holypkg and verifies their SHA-256
digests before missing-only repair. The test CA key stays in the build out-
put and is not included in the image or documentation bundle. The follow-
ing make variables are required:
STATIC_HOLYPKG
Previously built musl-static holypkg executable.
STATIC_HOLYINSTALL
Previously built musl-static holyinstall executable. The builder
checks its architecture and nolibc runtime, packages it with holyin-
stall(8), and the guest calls its disk subcommand on the disposable
QEMU target and tests package plan/apply in a separate target root.
STORAGE_TOOLS_PACKAGE
Native musl-static package with sfdisk, mkfs.fat, mke2fs and the
Limine command, required by INSTALL_TEST=1. make bootstrap-storage
builds it from pinned util-linux, dosfstools, e2fsprogs and Limine
inputs.
DOAS_PACKAGE
Native musl-static OpenDoas package with a root-owned setuid exe-
cutable, required by INSTALL_TEST=1. The guest stages it from the
live cache and installs it in the reviewed holyinstall set plan with
approval bound to the artifact SHA-256.
STATIC_CC
Path to the musl C compiler driver for the early init helper.
BUSYBOX_PACKAGE, DINIT_PACKAGE, MDEVD_PACKAGE
Native .holy outputs of the matching bootstrap targets.
GLIBC_PACKAGE, MUSL_PACKAGE
For dual-libc, the native bootstrap libc packages with private lit-
eral loader paths. bootstrap-glibc and bootstrap-musl produce the
tested input layouts.
GLIBC_CC, MUSL_CC
Compilers for the two dynamic C fixtures. GLIBC_CC defaults to gcc;
MUSL_CC must name a musl compiler. The image build checks their ac-
tual runtime requirements against the selected libc payloads before
installation.
KERNEL_IMAGE, KERNEL_VERSION
Local Linux kernel image and its expected uname release. The se-
lected kernel must include initramfs/gzip, devtmpfs, proc, sysfs,
tmpfs, devpts and serial console support. The local kernel-image
profile includes no kernel modules. The initramfs excludes kernel
modules. A source kernel package may install modules in the rootfs;
the dummy.ko fixture is loaded during the QEMU boot probe when
present with its matching modules.dep entry.
LIMINE_DIR
Directory containing limine-bios.sys, limine-bios-cd.bin and limine-
uefi-cd.bin. x86_64 also requires BOOTX64.EFI. Use the matching host
limine tool.
OUTPUT
New output directory. Existing directories are refused.
Host tools include unshare with user namespaces, a C toolchain, dracut,
xorriso, Limine, GNU file tools, gzip, cpio, Python 3 and QEMU. DRA-
CUT_BASE selects dracut's helper directory; the default is /usr/lib64/dra-
cut. Compilation and installation run as mapped root in a user namespace
owned by the ordinary caller. Host root invocation is refused.
INPUTS AND ROOT
The builder snapshots the selected native packages, normalizes numeric own-
ership to root and directory modes to 0755, regenerates manifests and
records the parent hashes and normalization in origin. These are new un-
signed local artifacts. The original archives remain in inputs/. For the
older BusyBox bootstrap package, its embedded-musl license moves to
usr/share/licenses/busybox/musl.COPYRIGHT, with the mapping recorded in
origin. The dynamic musl package owns its own license file.
The static core binary, selected kernel, Limine data and Holy boot configu-
ration also become native packages. The holy-base metapackage requires the
resulting eight packages, including holyinstall. INSTALL_TEST=1 also se-
lects the doas package and records its setuid approval. The dual-libc pro-
file adds the two runtime packages and two C probe packages. holyinstall
asks holypkg to resolve the selected set, writes install.preview and a
frozen install.plan, then applies that plan with one writer lock and one
generation change into root/. The preview records selected providers and
requirements; the plan records artifacts and architecture approvals. The
builder prepares the profile's directories first. It does not extract for-
eign distro roots or modify the host package database.
For ROOT_STORAGE=ext4, mke2fs copies that prepared root into a 512 MiB raw
root.ext4 without mounting it on the host. Limine passes holy.root=/dev/vda
and holy.rootfstype=ext4. holy-init mounts the disk, moves runtime mounts
and executes BusyBox switch_root followed by dinit. The ISO still provides
kernel and initramfs; this is not yet an independently installed bootable
disk.
ROOT_STORAGE=gpt-ext4 creates a new 1 GiB regular disk.raw. The builder
uses holyinstall disk plan/apply and includes the reviewed plan hash in the
boot plan. The GPT contains a 1 MiB BIOS Boot partition, a 256 MiB FAT32
ESP and a 765 MiB ext4 root. It verifies the partition layout, copies the
kernel, initramfs and Limine configuration to the ESP, reads those files
back and compares their bytes. It then uses holyinstall disk finalize-
plan/apply to bind the ESP, root and disk images by SHA-256 before copying
their bytes into the partitions. The finalize journal records the write
stages. Limine BIOS stages use partition 1; on x86_64 the UEFI loader uses
EFI/BOOT/BOOTX64.EFI. The kernel mounts /dev/vda3. Host filesystems are not
mounted and physical disks are not accepted as output.
The GPT profile passes holy.esp=/dev/vda2. holy-init mounts that FAT parti-
tion at /boot before starting dinit, using nosuid,nodev,noexec and masks
matching the kernel package's file and directory modes. The kernel must in-
clude vfat, the default FAT codepage and NLS charset. The boot probe checks
the mount, kernel/initramfs/config presence and a plan-bound write that
survives reboot. Installed package checking validates the mounted kernel
payload against its recorded manifest. This exposes the actual boot files
to subsequent operations; coordinated kernel/initramfs update transactions
remain to be implemented.
The profile installs merged-/usr links, a locked root account and the dinit
services boot, console, mdevd, coldplug and holy-test. mdevd reports readi-
ness through a pipe before synchronous coldplug starts. The fixture checks
that coldplug applied the /dev/null permission rule. This is device-manager
coverage, not client libudev compatibility.
INITRAMFS AND ISO
The private dracut module copies the installed root with preserved owner-
ship and file contents. dracut includes only the Holy module, with no host-
only configuration, host runtime dependencies, microcode or kernel modules.
The builder points dracut's sysroot at the installed root. It extracts the
finished initramfs, compares payload bytes, modes and symlinks with that
root, and checks each ELF with holypkg. Only dracut's module list, build
parameters and generated loader caches may be additional files. The audit
rejects unexpected loader aliases. dracut uses ldconfig -X so package sym-
links remain unchanged. The audit also rejects non-static ELF outside the
five declared libc and probe paths in dual-libc; static-core rejects all
non-static ELF. The prior package-set scan validates the libc/probe graph
and payload manifests. Omitted files are listed in the build record and re-
main absent from the image until guest recovery. holy-init mounts the live
runtime filesystems and executes dinit. Limine loads the kernel and
initramfs from the ISO. The default entry selects the guest test service
with holy.test=1. Remove that argument in Limine's entry editor to reach
the BusyBox console service.
The output includes packages/, inputs/, root/, initramfs.img, holy-
ARCH.iso, build.log, build.record, root-check.record, a frozen plan and its
boot-plan SHA-256. The record lists artifact hashes and the build result.
Input retention permits repeating an experiment; bit-identical ISO repro-
duction is not established.
INSTALLER VM FIXTURE
INSTALL_TEST=1 selects an x86_64 static-core RAM ISO whose default service
installs the selected base artifacts on a separate QEMU guest disk. The
runner creates a blank, read-only 1 GiB raw disk and attaches a writable
qcow2 overlay to the guest. The live guest uses holyinstall to partition
that disk, create FAT32 and ext4 filesystems, and install Limine's BIOS
stage. Its static storage tools come from STORAGE_TOOLS_PACKAGE, which the
builder installs into the live image but excludes from the installed base.
The guest mounts the target root, stages its local .holy archives, reviews
and applies one holyinstall package plan containing doas and its setuid ap-
proval. When the live image has configured sources, the guest registers the
same source IDs in the target root, copies embedded pinned mirrors, and
binds source-attributed artifacts in that plan. It verifies the installed
source records before declaring the package set committed. The source con-
fig remains at /etc/holy.conf on the installed disk. It generates the in-
stalled man bundle, and copies kernel, initramfs and Limine files from the
ISO to the ESP. The runner shuts down that guest after serial success mark-
ers, then forks the installed overlay for separate BIOS and UEFI disk
boots. Both guests check dinit, BusyBox, holypkg, package state, documenta-
tion and the mounted ESP. The install-test image contains a disposable lo-
cal user and a static login probe. Each installed boot checks rejection of
an incorrect password and an authenticated shell with UID 10001. The same
PTY probe enters the password requested by doas and checks UID 0 from the
policy's selected BusyBox command. The fixture account and scoped doas.conf
are only present when INSTALL_TEST=1. The UEFI boot uses fresh writable
firmware variables.
The holy-install-vm-7 report records each QEMU command, observed and miss-
ing serial markers, serial hashes, ISO and disk hashes, the guest disk-plan
hash, base-image integrity and checks reached by the guest. The i686 BIOS
install-to-disk fixture passed under QEMU/TCG. Its report lists the account
menu, PAM/NSS, network and i686 UEFI as untested. INSTALL_FIRMWARE=bios
runs only BIOS and records UEFI as untested; the x86_64 default both mode
requires UEFI_CODE and UEFI_VARS, which default to the local QEMU edk2 im-
ages. This fixture verifies package installation and boot from a separate
disk; it does not claim the whole interactive installer acceptance matrix.
Run an existing fixture ISO with make check-install-vm ARCH=i686 ISO=FILE
BOOT_PLAN=SHA256 for i686, or omit ARCH for x86_64. The i686 install fix-
ture requires a BusyBox package with chown, login, getty, su, passwd, ad-
duser and addgroup applets.
QEMU
make check-qemu ARCH=x86_64 ISO=FILE BOOT_PLAN=SHA256 runs the test sepa-
rately. IMAGE_PROFILE defaults to dual-libc. LIBC_BOOT_STATE must match
the image's declared initial state. The runner requires the corresponding
recovery markers; a static-only boot cannot pass a dual-libc test.
ARCH=i686 chooses qemu-system-i386 and requires an i686 guest image. The
builder reads the x86 kernel boot header and rejects a kernel with the
other target architecture before it creates an image. REPORT_DIR chooses
an existing report parent directory, defaulting to the system temporary di-
rectory. QEMU_TIMEOUT bounds each boot in seconds (1..600, default 120).
QEMU_PROBE_TIMEOUT bounds each guest stage (1..600, defaulting to
QEMU_TIMEOUT). The guest emits stage-start markers; a repeated marker does
not renew its deadline. The runner tracks repeated stages within each boot;
the report names the boot and stage that ran or timed out. QEMU_ACCEL=auto
selects usable KVM or TCG; kvm and tcg force the accelerator, with unavail-
able KVM returning 6.
FIRMWARE=bios is the default. FIRMWARE=uefi requires UEFI_CODE and
UEFI_VARS. The runner copies both firmware inputs and gives the guest a
new writable VARS file. It copies and hashes the ISO before starting QEMU.
Networking is disabled by default, and no host writable disks or directo-
ries are attached. For NETWORK_RECOVERY=fixture, the runner creates a pri-
vate network namespace, raises its loopback, serves the two frozen arti-
facts over HTTPS and connects QEMU user networking only to that namespace.
Its host has no external route. The guest uses 10.0.2.15/24, resolves fix-
ture.holy.test through the fixture DNS server and fetches both archives by
that HTTPS name. The report records DNS queries, the CA and artifact
hashes, HTTP requests and recovery markers. This fixture does not test a
public CA bundle or external network access.
ROOT_DISK supplies a self-contained regular raw or qcow2 disk image for the
persistent dual-libc test. An external qcow2 backing chain is rejected. The
runner copies the input to a read-only base, creates a qcow2 overlay and
attaches only that overlay through virtio-blk. It verifies the copied base
hash after QEMU exits and records the overlay hash. QEMU_KEEP=1 retains the
overlay and copied inputs for inspection. The default removes those tempo-
rary files after writing the report and serial log. Copying a live input is
marked unverified for consistency; the caller must provide a stopped image
or stable snapshot for a precise trial. ROOT_IMAGE alone remains an op-
tional report input and does not attach a disk.
The guest verifies that / is ext4, completes recovery and its package
probes, records the plan ID under /var/lib/holy-boot-test/reboot, calls
static sync and requests reboot. On the second boot it requires that wit-
ness and intact restored libc; it repeats PID 1, shell, device, dynamic/IPC
and package probes without another libc repair. The runner keeps each
boot's markers separate, rejects repeated or out-of-order boot numbers and
requires both complete contracts. Each boot has its own QEMU_TIMEOUT dead-
line. RAM tests still use one boot and QEMU's no-reboot mode.
Guest markers must confirm the expected plan and architecture, dinit as PID
1 by /proc/1/exe, a BusyBox shell probe, mdevd/coldplug and a complete lo-
cal two-package dependency-set install/check/remove, including refusal to
remove the referenced provider first. The guest checks the critical exe-
cutables' static ELF classification. For static-core it also checks absence
of the four expected dynamic loaders. For dual-libc it verifies the de-
clared initial presence/absence of the runtime files, diagnoses broken
providers, reads the local LZ4 archives and executes missing-only repair
inside the guest. It runs allocation, thread and clock probes with each
libc, checks both standard loader links and exchanges data through a pipe
in both directions between the two ABIs. KERNEL_VERSION additionally re-
quires the matching release marker. A failure marker overrides pass mark-
ers. The runner sends QMP quit after the probes or a boot timeout, waits
up to five seconds, then kills a process which did not exit. Process launch
or exit status alone cannot pass a boot test.
Each run retains serial.log, QEMU output, a text report and report.json
(holy-qemu-report-2). Reports include argv, accelerator, firmware, input
hashes, elapsed time, stage deadlines, exit reason and per-marker status.
The report records whether temporary inputs and the writable overlay re-
main. Persistent tests additionally record both boot results, the first-
boot completion time, root storage type and whether the read-only base
stayed intact. The probe list records each stage's elapsed time and
pass/fail/unknown status. Optional KERNEL_IMAGE, INITRAMFS and ROOT_IMAGE
inputs add their hashes. They identify caller-supplied files, not guest
measurements of running code.
Retained recovery matrix
make check-recovery-matrix REPORT=NEW_FILE REPORTS="REPORT_PATHS" validates
eight retained QEMU report.json files: present, glibc, musl and both ini-
tial states, each under BIOS and UEFI. Each case must contain two complete
ext4 boots. The validator checks per-boot serial evidence and reported
checks, including PID 1 identity after reboot. It rehashes the retained
ISO, raw root, recovery overlay and UEFI code snapshots. BIOS and UEFI runs
of each initial state must use the same plan, ISO and raw root. Missing or
duplicate cases, changed snapshots and incomplete second boots fail the ma-
trix.
The command creates a new holy-recovery-matrix-1 JSON report containing
each input report and serial hash, image identities, acceleration and
elapsed time. The output must not exist. Run the guest tests before invok-
ing this command; the validator reads retained evidence and does not start
new VMs. Local hashes bind the retained files, not an authenticated pub-
lisher. Preserve each run directory alongside the summary for revalidation.
COVERAGE
BOOT_MEDIA=disk selects standalone disk boot in the QEMU runner and re-
quires ROOT_DISK. ISO must be empty; the runner attaches no CD-ROM. Each
run uses its own qcow2 overlay and records boot_media, two boot contracts
and the unchanged copied base. gpt-ext4 produces disk.raw, disk.plan, disk-
layout.json and limine.conf instead of an ISO. BIOS and UEFI remain sepa-
rate test runs.
The bootstrap glibc package currently contains libc.so.6 and its loader
with licenses, not the complete SDK, locale archive, NSS modules or auxil-
iary glibc libraries. The reference image still needs the installer, ordi-
nary networking and public CA packaging. The ext4 fixture uses an ISO ker-
nel; gpt-ext4 loads its kernel and initramfs from the disk's ESP. Both test
recovery followed by reboot on a persistent root overlay. The GPT builder
is not the interactive installer. Kernel updates still require integration
with ESP contents and the generated initramfs. Those gates remain separate
from this boot result. The glibc/musl/both damage fixtures retain package
records; remove-both exercises package removal and reinstallation after a
persistent reboot. The repository documentation bundle contains Holy
pages. The image builder uses holypkg docs after its package transaction to
generate /usr/share/holy/llm.txt from the installed man sources, including
dinit. This generated file is separate from package-owned source pages;
its SHA-256 is recorded in the frozen image plan and /etc/holy/docs.sha256.
The output directory retains llm.txt and docs.record with the coverage sum-
mary. Packages without man pages and unsupported page encodings appear in
that bundle.
On each boot, the guest verifies the shipped bundle's digest and regener-
ates it with static holypkg after any libc recovery. It compares the com-
plete contents, normalizing only the final summary's database generation
because package transactions between boots advance that counter. A documen-
tation failure fails the boot contract. The bundle contains attributed roff
sources rather than rendered upstream text. QEMU virtual devices do not es-
tablish physical GPU support.
make check-image-docs IMAGE_DIRECTORY=DIRECTORY tests an ext4/dual-
libc/both build's documentation failures. It copies root.ext4 into two dis-
posable disks, changes the generated bundle in one and the installed
holypkg man source in the other, then boots each through the original ISO
under BIOS/TCG. Both guests must stop the boot contract at the documenta-
tion stage. The original root image's hash must remain unchanged. The fix-
ture requires debugfs and the QEMU tools; it never edits the original image
or mounts a host disk.
HARDWARE
make check-hardware runs outside QEMU against a physical NVIDIA GPU using
Nouveau and Mesa NVK. It checks the kernel driver and DRM render node, se-
lects the NVK Vulkan device, presents 30 frames with vkcube, and measures
OpenGL frames with glxgears. glxinfo must report an accelerated NVIDIA ren-
derer. lspci, vulkaninfo, vkcube, glxinfo, glxgears, timeout, a display
session and a writable render node are required.
The default HARDWARE_SCOPE=holy requires a valid installed Holy database on
the running system. Missing inputs return 6. REPORT defaults to out/hard-
ware.json; the holy-hardware-1 JSON records probe commands, output, elapsed
times, driver selection and frame counts. A failure returns 1. HARD-
WARE_SCOPE=host permits a diagnostic probe on another distribution and
marks release_gate=false. A host result cannot certify Holy's packages or
boot image. The frame probes establish presentation and an FPS count, not
pixel-correct rendering or broader game compatibility.
SOURCES
Limine configuration and ISO layout: https://github.com/limine-boot-
loader/limine/blob/v12.x/CONFIG.md and https://github.com/limine-boot-
loader/limine/blob/v12.x/USAGE.md. dracut module interface:
https://github.com/dracut-ng/dracut-ng/blob/main/man/dracut.modules.7.adoc.
QEMU block device and backing-file options: https://www.qemu.org/docs/mas-
ter/system/invocation.html.
SEE ALSO
holy-init(8), holypkg(8)
Holy September 2026 HOLY-IMAGE(7)
holy-init.8 source=holy version=development sha256=d3a6d3e0068c68f2c3a615b25f6cd5479a5ce1cc25827a9ebc764f2156e7d268
HOLY-INIT(8) System Administration HOLY-INIT(8)
NAME
holy-init - prepare live runtime mounts and execute dinit
DESCRIPTION
holy-init runs as PID 1 from a live initramfs. It mounts devtmpfs, attaches
standard descriptors to /dev/console, makes mount propagation private, and
mounts proc, sysfs, tmpfs at /run and /tmp, and a new devpts instance. It
then executes /sbin/init, supplied as a link to dinit by the image profile.
dinit retains PID 1. Mount and exec failures print a diagnostic and leave
the process waiting for intervention; they do not report a successful boot.
The boot profile uses /etc/dinit.d, a control socket at /run/dinitctl,
PATH=/usr/bin and HOME=/root. The default service is boot. An exact
holy.test=1 kernel argument selects holy-test instead. Other kernel words
are not interpreted as shell code.
Calling holy-init outside PID 1 returns 2 before mounting anything. With-
out holy.root it boots an in-memory live root. holy.root=/dev/DEVICE se-
lects an ext4 disk root; holy.rootfstype=ext4 is the supported filesystem
selector. Empty or repeated holy.root arguments are errors. The helper
waits up to 30 seconds for the block device, mounts it at /newroot, veri-
fies that the target init is executable and moves /dev, /proc, /sys, /run
and /tmp there. It then executes the static BusyBox switch_root applet
with the dinit argv. The process retains PID 1. Failure does not fall back
to the RAM root. Device discovery must be available through built-in ker-
nel drivers/devtmpfs.
holy.esp=/dev/DEVICE mounts a FAT ESP at /boot within the disk root before
switch_root. It requires holy.root and a real block device; empty or re-
peated ESP arguments fail. The boot directory must be a directory, not a
symlink. The mount uses nosuid,nodev,noexec, root ownership, file mode
0644 and directory mode 0755. The kernel must provide vfat and its required
NLS tables. Failure leaves the boot incomplete rather than using a hidden
rootfs copy. Encrypted roots, UUID discovery, modules and other filesys-
tems are not yet implemented by this helper.
BUILD
make builds holy-init with the configured C99 compiler. make static builds
it with the selected musl toolchain. The image builder also compiles it us-
ing its explicit STATIC_CC input and verifies static ELF classification.
SEE ALSO
holy-image(7), holypkg(8)
Holy September 2026 HOLY-INIT(8)
holyinstall.8 source=holy version=development sha256=4688af7110d201daacae2a11b7e11c935d8eb000991eed9feb9913f0583b0ddf
HOLYINSTALL(8) System Manager's Manual HOLYINSTALL(8)
NAME
holyinstall - prepare a disk or image and install packages to an existing
Holy root
SYNOPSIS
holyinstall --menu --config FILE --plan NEW_FILE [--holypkg FILE]
holyinstall --config FILE --plan NEW_FILE [--holypkg FILE]
holyinstall --apply PLAN [--holypkg FILE]
holyinstall disk plan --config FILE --output NEW_PLAN
holyinstall disk show --plan PLAN
holyinstall disk apply --plan PLAN --confirm IMAGE_OR_DEVICE
holyinstall disk finalize-plan --disk-plan PLAN --esp FILE --root-image
FILE --output NEW_PLAN
holyinstall disk finalize-apply --plan PLAN --confirm IMAGE
DESCRIPTION
holyinstall supports a disk-image preparation stage and a separate package
transaction for a pre-mounted root. Disk preparation does not mount the im-
age or connect it to the package transaction. The target must already con-
tain an initialized holypkg database and cached are dependency candidates.
It invokes holypkg with argv, without a shell. The default manager path is
/usr/bin/holypkg. It must be Holy's C package manager; the separate Slack-
ware converter named holypkg does not implement this transaction interface.
The caller must already have permission to modify the target; this stage
does not invoke doas.
--menu opens a numbered text menu on a terminal. It shows the current root,
package count and selected disk image. It edits the root and disk image,
and opens a package list with add, delete and back actions. Preview calls
the same holypkg planner as config-driven mode. Save writes a normalized
config atomically. Prepare plan writes the same frozen plan as noninterac-
tive mode. Install shows the root, set hash and artifact count, then re-
quires the literal answer yes. Abort and end-of-input leave the target un-
changed before apply. Without a TTY, --menu exits with status 3. The text
menu accepts [install] and [disk]; it rejects other sections to avoid
silently discarding them on save.
The menu saves [disk] in the same config as [install]. It creates a sepa-
rate disk plan at PACKAGE_PLAN.disk, shows the exact image path, GPT geome-
try and the plan's first and last MiB hashes. It asks for that full path
before formatting. Disk selection may be left unset while working on pack-
ages. Disk planning and package installation remain separate steps; select-
ing an image does not mount it as the package target.
Planning runs holypkg db plan-set read-only. It prints the selected
providers and requirements, then creates a new plan file with mode 0600.
The plan binds the SHA-256 of the effective parsed config, canonical tar-
get-root path, target directory device/inode, ordered artifact identities
and holypkg set hash. An existing plan file is not overwritten. Apply reads
only the fixed plan schema; it never reopens the user config or its in-
cludes under elevated privileges. It verifies the target directory and
passes the recorded set hash to holypkg db apply-set. holypkg rejects a
changed database generation, artifact, ownership or dependency result. Ap-
ply retains holypkg's incomplete transaction journal if mutation fails.
The config uses the holy.conf lexer, including relative includes and quoted
paths. A minimal config is:
[install]
root "/mnt/holy"
artifact SHA256_OF_ROOT_PACKAGE
artifact SHA256_OF_DEPENDENCY_CANDIDATE
accept-arch SHA256_OF_CONFIRMED_CROSS_ARCH_PACKAGE
accept-privileged SHA256_OF_REVIEWED_SETUID_PACKAGE
source SHA256_OF_SELECTED_PACKAGE REGISTERED_SOURCE_ID
SHA256 values must be lowercase. The root path cannot be /. Config comments
do not change the effective-config hash. Changes to parsed values or arti-
fact order require a new plan. The plan must be reviewed before apply; the
config is no longer consulted at that step.
accept-arch names an artifact already selected in [install]. It preserves
an explicit architecture-placement decision in plan format 2. Preview and
apply pass the same approval to holypkg. It permits placement; it does not
claim the target executable can run on the host. The text menu toggles this
decision in the package list and shows accepted artifacts before installa-
tion.
accept-privileged names a selected artifact whose setuid executable and
full payload the caller has reviewed. Plan format 3 records its exact
SHA-256 and apply passes the same decision to holypkg. Without the ap-
proval, planning returns decision-required. The package menu toggles this
decision separately from architecture placement. A changed artifact needs a
new approval and plan.
source associates one selected artifact with an active registered source
ID. The target database must already contain that registration. Plan for-
mat 4 freezes each artifact-to-source binding and passes it to holypkg dur-
ing both planning and apply. Unknown sources, duplicate bindings and
changed source registries fail before package mutation. The text menu can
set or clear a source ID and keeps the binding when saving config.
DISK PREPARATION
The disk command accepts a regular file or a block disk of at least 1 GiB.
The supported layout is GPT with a 1 MiB BIOS boot partition, a 256 MiB
FAT32 ESP and an ext4 root partition. Image mode uses sfdisk, mkfs.fat and
mke2fs, and requires losetup to confirm that the image is not attached to a
loop device. A minimal image config is:
[disk]
image "/tmp/holy.img"
layout gpt-ext4
Create the image before planning, for example with truncate. Planning
prints the exact image path, device/inode identity, size and partition
geometry. It hashes the first and last MiB and writes a new mode-0600 plan.
Apply requires --confirm to equal the canonical image path in that plan. It
checks the identity and both hashes again, then records prepared, parti-
tioning and formatting stages in PLAN.journal. An existing journal blocks
another apply. The show command prints the saved geometry without changing
the image. After an interrupted stage, inspect the image and journal be-
fore starting a new plan. The command does not silently repeat a format op-
eration.
For a block disk, use device instead of image in [disk]. Planning records
the disk node, block-device number, size, serial, first and last MiB
hashes, and SHA-256 of the four static tools used by apply: sfdisk,
mkfs.fat, mke2fs and Limine. Apply requires the exact device path as --con-
firm and rechecks those inputs before mutation. It holds an exclusive disk
descriptor, writes the GPT, asks the kernel to reread the partition table,
checks the partition geometry, formats FAT32 and ext4, and installs the
Limine BIOS stage. The journal records each stage. This operation destroys
existing partitions and filesystems. It does not mount the new root or in-
stall packages.
Disk preparation destroys existing image contents. It does not mount the
partitions or generate an initramfs. The finalize-plan command requires a
committed disk preparation journal, a valid GPT, an exact-size ESP image
and an exact-size ext4 root image. It records the full SHA-256, device, in-
ode and size of the disk, both input images and /usr/bin/limine in a new
mode-0600 plan. Review that plan before applying it. Finalize-apply re-
quires the exact disk path as --confirm and rechecks all inputs before
changing the disk. It copies the two images into their partitions, verifies
the written bytes, installs the Limine BIOS stage and verifies GPT. The ad-
jacent journal records each stage. A changed input requires a new plan; an
existing finalize journal requires inspection before recovery. The command
does not configure UEFI, mount the partitions or prove the image can boot.
Boot and account menus and the full interactive install acceptance matrix
remain pending.
The optional install VM fixture described in holy-image(7) gives the live
guest a blank disk. holyinstall creates the GPT and filesystems and in-
stalls the Limine BIOS stage inside that guest before applying a package
plan to its root. The runner then boots the installed disk through BIOS and
UEFI.
EXIT STATUS
0 means the requested step completed. 1 means an operational error. 2 means
invalid arguments, config or plan. Disk apply returns 3 for a changed image
or missing confirmation, 5 for an existing journal or failed mutation, and
6 for a missing image, tool or available capability. holypkg statuses 3
through 6 propagate from the package transaction.
SEE ALSO
holypkg(8), holy.conf(5), holy-image(7)
HOLYINSTALL(8)
holy-package.5 source=holy version=development sha256=c1e0877db91580d7382074adbb37c3c6bc6ea0b94f647763f08d6937bdba2700
HOLY-PACKAGE(5) File Formats HOLY-PACKAGE(5)
NAME
holy-package - prototype native archive and payload manifest
DESCRIPTION
A .holy archive is a POSIX tar/PAX stream compressed as a standard LZ4
frame. HOLY/meta contains one key and one value per line. The required
fields are format (holy-package-1), name, version, release, os, arch and
libc. An unknown requires-feature field fails inspection. Optional x-ver-
sion-family records the version comparator family and is a scalar: dupli-
cates fail inspection. The current resolver supports constrained package
requirements for pacman, Debian and explicitly tagged Holy native versions.
It compares versions within the consumer's declared family; unknown fami-
lies remain inspectable but cannot acquire guessed version semantics. Un-
versioned file requirements match exact nondirectory paths in verified pay-
load manifests. Declared file claims alone do not establish file ownership.
Unversioned command requirements match executable payloads in the standard
bin directories. Links must resolve to an executable in the same artifact;
declared command claims alone do not prove one exists. Native packages opt
in with x-version-family holy. Their version and release use decimal compo-
nents separated by dots, with an optional lowercase prerelease suffix in-
troduced by hyphen or tilde, such as 7.3-rc4. Invalid Holy native version
or release fields fail inspection. Native comparison treats absent trailing
numeric components as zero, compares numbers without fixed-width overflow
and places prereleases before final versions. Pacman imports store the
complete foreign epoch:version-release in version, and use release for the
native artifact revision.
The inspector accepts os=linux or windows, arch=x86, x86_64 or noarch, and
libc=glibc, musl or nolibc. noarch requires nolibc. These checks validate
declared metadata values; they do not scan ELF files to prove ABI or prove
that a purported noarch payload has no machine code.
The separate holypkg scan command examines regular ELF payload files and
each hardlink path, and rejects known arch/libc tag mismatches. The basic
info and verify commands do not perform this ABI check.
The scanner accepts ET_EXEC without PT_INTERP and PT_DYNAMIC as nolibc, and
ET_DYN requiring libc.so.6 as glibc evidence, including its basename in
literal DT_NEEDED paths. Architecture-specific musl loader or libc names
and the native libc.musl-x86_64.so.1 / libc.musl-i386.so.1 SONAMEs provide
musl classification evidence. The native libc.so.6 and matching glibc
loader SONAMEs classify those glibc runtime files. A generic libc.so name
does not. Foreign import writes a SONAME capability only after reading a
classified ET_DYN payload file and records that file as evidence. It re-
jects ELF files whose runtime remains unknown. A plugin without such evi-
dence needs classification and review before a package can pass scan.
The scanner accepts an ET_REL kernel module only under usr/lib/modules/RE-
LEASE/kernel/ with a .ko suffix, matching package architecture, libc=nolibc
and a .modinfo vermagic release matching its path. It does not treat mod-
ule symbols as userspace providers. Other ET_REL objects remain unknown for
ABI classification.
The archive contains one regular file at each of HOLY/meta, HOLY/files,
HOLY/deps, HOLY/provides, HOLY/hooks, HOLY/origin and HOLY/transform. The
inspector checks their presence and type. Empty metadata placeholders can
pass this structural check; it does not validate dependency, capability,
hook or origin content. The separate requirements and provides commands
parse supported subsets of HOLY/deps and HOLY/provides.
The prototype verifier reads HOLY/files. For each regular payload file un-
der DATA/, one manifest line contains twelve whitespace-separated tokens:
file PATH MODE OWNER GROUP UID GID SIZE SHA256 FLAGS XATTRS HARDLINK-GROUP
MODE is octal; UID, GID and SIZE are decimal. PATH is relative to DATA.
SHA256 has 64 hexadecimal digits. FLAGS accepts none, config, mutable or
config,mutable for regular files. The verifier rejects flags on directo-
ries, symlinks and hardlinks. Installed checks still compare mutable files
with their manifest; update policy for their local changes is not imple-
mented. XATTRS=-. Independent files use HARDLINK-GROUP=-. A symlink uses
one additional TARGET token, SIZE=0 and SHA256=-. A hardlinked file uses
TYPE=hardlink, a group identifier in HARDLINK-GROUP and one extra TARGET
token naming a regular file in DATA. The target file carries the same
group identifier. SIZE and SHA256 describe the target content. The verifier
rejects chained hardlinks and checks mode, UID and GID against the target.
A directory uses the same twelve tokens as a file, with TYPE=dir, SIZE=0
and SHA256=-. The verifier rejects symlink targets that escape the root by
lexical traversal. The text lexer follows holy.conf(5), so quotes and es-
capes can preserve spaces in paths. Every archived directory except DATA
itself needs a manifest record. The verifier does not support special
files, ACLs or extended attributes. The verifier rejects DATA entries that
carry archive xattrs or ACLs, even if HOLY/files says XATTRS=-. The planned
full format includes those attributes.
The narrow database apply command installs approved data, native static ELF
files and relative symlinks into existing or explicitly declared safe di-
rectories. Symlinks require mode 0777 and the caller's numeric ownership.
The transaction records their targets, checks them without following links,
and removes only intact owned entries. Direct hardlinks share the verified
regular target's inode; one regular anchor belongs to each named group. In-
stallation defers hardlinks until regular payload files exist, indepen-
dently of archive order. Absolute symlink installation remains unsupported.
Cached updates preserve hardlink groups across content and mode changes,
member additions/removals, anchor moves and group splits/merges. The file
plan names a reserved staging link for each new group; recovery validates
both content and inode relationships. The pack command writes and verifies
this archive format from a prepared tree of regular files, directories,
symlinks and hardlinks. It selects a deterministic regular anchor for each
inode group; manifest generate uses the same selection. The verifier does
not verify dependencies, signatures, symbolic owner names or the installed
filesystem. A full format implementation must verify file ownership, links,
xattrs, hooks, origin and transaction semantics before accepting an arti-
fact for installation.
FOREIGN IMPORT RECORDS
The pacman and Debian importers preserve original package metadata beneath
HOLY/foreign/pacman and HOLY/foreign/deb. HOLY/origin binds these records
to the exact original archive hash and retains original field values and
line numbers. The source-name field is provenance, not authority to assign
a trusted installed source-id.
A requirement of kind foreign preserves an unsupported source expression.
The current solver reports unsupported semantics instead of dropping that
requirement. Foreign hooks may be skipped by an explicit artifact-scoped
--skip-hooks decision during set installation. The installed package then
carries hooks-state with the hash of HOLY/hooks and reports installed-un-
configured in check. Native postinstall records can be reviewed and exe-
cuted through db configure-plan and db configure-apply. A completed hooks-
state retains the same hash. Foreign script execution, preinstall, editing
and service consent remain unimplemented. For a reviewed update preserving
a modified config, the installed instance keeps the exact archived
HOLY/files as package-files. Its files record describes the preserved pub-
lic file and the new PATH.holy-new. config-state binds the source artifact,
raw manifest hash, installed manifest hash and transaction plan hash. The
archive digest and local installed manifest digest remain distinct.
HOLY/transform records changes already applied to the normalized payload;
installation never executes that record. Archive verification does not au-
thorize hook execution.
The AppImage converter records family appimage with the converter holy-ap-
pimage-1, the original image hash, the entry point and mode extract in
HOLY/origin. Its payload keeps the whole extracted AppDir under
/usr/lib/holy/private/NAME/appdir/, a link to it at /usr/lib/holy/pri-
vate/NAME/usr/bin/NAME.appimage and a generated launcher at /usr/bin/NAME;
the launcher is a script, so the package names an interpreter requirement
like any other script. HOLY/transform records the private layout, the
launcher, the rewritten desktop entry, the path view a link needs, the
dropped image-level sandbox and the absence of a desktop database hook. The
version, arch and libc are read from the payload rather than assumed, and a
converted package therefore carries the requirements that payload implies.
The Snap converter records family snap with the converter holy-snap-1, the
original image hash, the manifest version, the command and mode extract in
HOLY/origin. Its payload keeps the whole snap root under /usr/lib/holy/pri-
vate/NAME/snap/ with the image at /usr/lib/holy/pri-
vate/NAME/usr/bin/NAME.snap and a generated launcher at /usr/bin/NAME; the
launcher is a script, so the package names an interpreter requirement like
any other script. HOLY/transform records the private layout, the launcher,
the base requirement, the dropped confinement, each dropped plug, and the
recorded hooks, environment names and command-chain entries. The base the
manifest names becomes a package requirement on that name, because the run-
time a snap store mounts has to come from a source on this system; a su-
perblock states no runtime, so that requirement names no libc. The name,
version, arch and libc are read from the manifest and the payload rather
than assumed.
The Scoop importer records family scoop with the converter holy-scoop-1,
the manifest digest, the artifact URL, its digest and mode artifact in
HOLY/origin. Its payload carries the verified artifact whole under
/usr/lib/holy/private/NAME/, with the directory the manifest declares as
extract_dir replacing the first component of every archive member path, and
the manifest copy sits in the output directory beside the package.
HOLY/transform records the private path, the extract_dir, the program the
manifest names and that the installer was dropped without a Wine require-
ment. Each dependency of the manifest becomes one package requirement on
the app name, and HOLY/hooks stays empty because nothing runs at install.
The WinGet importer records family winget with the converter holy-winget-1
through the same writer, so its payload, its private path, its transform
record and its empty HOLY/hooks match the Scoop package: only the manifest
fields, the requirement ids and the report wording name the catalog instead
of the bucket.
The Nix closure importer records family nix with the converter holy-nix-1,
the capture digest, the store path, its hash and mode closure in HOLY/ori-
gin. It emits one package per store path, each keeping its own store path
whole under /usr/lib/holy/private/NAME/store/STORE_PATH/. A store path that
states an entry point also carries a link at /usr/lib/holy/pri-
vate/NAME/usr/bin/NAME and a generated launcher at /usr/bin/NAME, so the
package names an interpreter requirement like any other script. HOLY/trans-
form records the private layout, the launcher, the absent store view, the
dropped store daemon and the reference count. Each reference the capture
declares to a store path it carries becomes one package requirement on that
name; a reference to a store path it does not carry becomes a requirement
whose original field is the store hash and which names no architecture or
runtime. Nix states no version, so every package records version 0 and the
store hash is its identity in x-nix-store-hash. HOLY/hooks stays empty be-
cause nothing runs at install.
The Solus eopkg importer records family eopkg with the converter holy-
eopkg-1, the artifact digest, the original version, the original architec-
ture, the PiSi format, the install tar digest and mode artifact in
HOLY/origin, and it writes x-eopkg-format, x-source-arch and x-eopkg-compo-
nent into the metadata. The install tar travels whole under
/usr/lib/holy/private/NAME/eopkg/, since a Solus layout is recorded rather
than claimed. HOLY/transform records the private layout, the install tar
digest, the dropped install script and COMAR object, the dropped delta and
the untrusted signature. Each runtime dependency becomes one exact package
requirement whose original field is the releaseFrom value the metadata
states, or - when it states none, since a distribution release is a prop-
erty of the repository rather than of the dependency. A SONAME the payload
needs and does not provide becomes a SONAME requirement, and the package
name is provided as a package capability. HOLY/hooks stays empty because
nothing runs at install.
INSTALLED GRAPH
New local installations use holy-instance-4 state, or holy-instance-5 when
an explicit non-native architecture placement decision was accepted. Ver-
sion 5 adds an architecture record with the reviewed host and unchanged
target. The decision is scoped to that artifact hash and does not certify
execution. The state includes a graph SHA-256; the graph file uses holy-
resolution-1 with scope artifact-candidates, a root artifact, sorted se-
lected artifact hashes and selected requirement edges. Each edge records
consumer hash, requirement ID, provider hash, path, kind and target using
the holy.conf lexer. The plan hash includes these canonical bytes.
Database operations reject a missing, modified, writable or symlink graph.
Legacy holy-instance-1 entries remain readable without a graph. Their ab-
sence of recorded provider choices does not establish dependency correct-
ness.
Source-associated installations also preserve the source fields introduced
in holy-instance-3. State records the registered source-id and SHA-256 of a
separate source record containing that ID and the alias at installation.
The graph and reason fields retain their version-2 semantics. The source
record is manager-owned state, not copied from the package. The database
rejects absent, changed, writable or symlink source records and disagree-
ment between the record and state. Renaming or deactivating the registered
source does not rewrite installed origin records. Check, removal, orphan
analysis and missing-only repair preserve this identity without requiring
the source to remain active.
New records include a provides file copied from HOLY/provides and bind its
SHA-256 in state. Empty provides is valid. Missing, modified, writable or
symlink provides records invalidate the database. Installed package aliases
can be located from these records; read-only database checks do not require
the archive cache. Constructing an installation plan still requires veri-
fied cached artifacts. Legacy holy-instance-1/2/3 records remain readable.
Alias discovery for a legacy record reads its verified cached artifact;
missing cache returns unavailable instead of silently treating unknown ca-
pabilities as absent.
The set installation interface supports package names and declared aliases,
scoped pacman version constraints and native dynamic packages with literal
absolute interpreter and DT_NEEDED provider paths. It records explicit/de-
pendency reasons. General loader search, automatic ELF rewriting remains
unimplemented. Slot conflicts compare source-id, name, os, arch and libc;
distinct artifacts in different slots may coexist with compatible file own-
ership. Reviewed cached update plans can replace one source-bound slot;
registering the same artifact in multiple slots remains unsupported. Miss-
ing-only cache repair preserves the installed graph and refuses changed or
partial files.
SEE ALSO
holypkg(8), holy.conf(5)
Holy September 2026 HOLY-PACKAGE(5)
holypkg.8 source=holy version=development sha256=8a486bc1f72410be12715f5b013dd33cfd354899ec6305f479c944480d2915a5
HOLYPKG(8) System Administration HOLYPKG(8)
NAME
holypkg - Holy local package tooling prototype
SYNOPSIS
holypkg add local:FILE [--candidate local:FILE ...] [--choose ID=SHA256]
[--associate-source ALIAS] [--associate SHA256=ALIAS ...] [--accept-arch
SHA256 ...] [--accept-privileged SHA256 ...] [--skip-hooks SHA256 ...]
[--root DIRECTORY] [--yes] [--noninteractive]
holypkg add SOURCE:PACKAGE [--catalog MIRROR] [--candidate SOURCE:PACKAGE
...] [--candidate-provider SOURCE:KIND:NAME ...] [--candidate-local
SOURCE=FILE.holy ...] [--choose ID=SHA256] [--answers FILE] [--accept-arch
SHA256 ...] [--accept-privileged SHA256 ...] [--root DIRECTORY] [--prepare
| --yes] [--noninteractive]
holypkg up SOURCE:PACKAGE [--prepare] [--output NEW_FILE] [--catalog MIR-
ROR] [--choose SHA256] [--arch ARCH] [--libc LIBC] [--accept-arch SHA256]
[--accept-privileged SHA256] [--root DIRECTORY] [--yes] [--noninteractive]
holypkg apply PLAN --sha256 PLAN_SHA256 [--root DIRECTORY]
holypkg import INPUT --source NAME --format pacman|rpm|deb|slack-
ware|apk|xbps|appimage|snap|scoop|winget|nix|eopkg|pkgbuild
|void|aports|slackbuild|rpmspec|debian|gentoo|pacstall|flatpak|home-
brew|guix
--output NEW_DIRECTORY [--public-key FILE (apk only)]
holypkg build RECIPE --output NEW_DIRECTORY [--environment host|clean|vm]
[--work NEW_DIRECTORY] [--jobs N] [--yes] [--noninteractive] [--keep]
holypkg convert PKGBUILD|APKBUILD|NAME.SlackBuild|NAME.spec|de-
bian|NAME.ebuild|NAME.pacscript|NAME.rb|NAME.scm|NAME.json|TEMPLATE
--source NAME --output NEW_DIRECTORY
holypkg apt index Packages[.gz|.xz|.zst] --sha256 HASH --source NAME --base
HTTPS_BASE/ --output NEW_DIRECTORY
holypkg apt sync HTTPS_URL --sha256 HASH --source NAME --base HTTPS_BASE/
--output NEW_DIRECTORY [--ca-file FILE]
holypkg apt sync-signed HTTPS_BASE/ SUITE COMPONENT ARCH --source NAME
--keyring FILE --output NEW_DIRECTORY [--inrelease] [--files] [--ca-file
FILE]
holypkg apt sync-source ALIAS SUITE COMPONENT ARCH --root DIRECTORY
--keyring FILE --output NEW_DIRECTORY [--inrelease] [--files] [--ca-file
FILE]
holypkg apt bind ALIAS SUITE COMPONENT INDEX_ARCH CATALOG --root DIRECTORY
holypkg apt search QUERY --catalog DIRECTORY [--source ALIAS --root DIREC-
TORY]
holypkg apt search QUERY --source ALIAS --suite SUITE --component COMPONENT
--index-arch ARCH --root DIRECTORY
holypkg apt info PACKAGE --catalog DIRECTORY [--source ALIAS --root DIREC-
TORY]
holypkg apt info PACKAGE --source ALIAS --suite SUITE --component COMPONENT
--index-arch ARCH --root DIRECTORY
holypkg apt fetch NAME VERSION ARCH --catalog DIRECTORY --output NEW_DIREC-
TORY [--source ALIAS --root DIRECTORY] [--ca-file FILE] [--import] [--re-
quire-file /PATH]
holypkg apt fetch NAME VERSION ARCH --source ALIAS --suite SUITE --compo-
nent COMPONENT --index-arch ARCH --root DIRECTORY --output NEW_DIRECTORY
[--ca-file FILE] [--import] [--require-file /PATH]
holypkg appimage inspect INPUT
holypkg appimage extract INPUT --output NEW_DIRECTORY
holypkg snap inspect INPUT
holypkg snap extract INPUT --output NEW_DIRECTORY
holypkg apk index APKINDEX.tar.gz --source NAME --base URL/ --output
NEW_DIRECTORY
holypkg apk verify-index FILE --public-key FILE
holypkg apk sync SOURCE REPO --root DIRECTORY --output NEW_DIRECTORY
[--sha256 HASH | --accept-unsigned HASH] [--ca-file FILE] [--public-key
FILE]
holypkg apk bind SOURCE REPO CATALOG --root DIRECTORY [--accept-unsigned
HASH] [--public-key FILE]
holypkg apk search QUERY [--catalog DIRECTORY | --source SOURCE --repo REPO
--root DIRECTORY]
holypkg apk info PACKAGE [--catalog DIRECTORY | --source SOURCE --repo REPO
--root DIRECTORY]
holypkg apk providers soname:NAME [--catalog DIRECTORY | --source SOURCE
--repo REPO --root DIRECTORY]
holypkg apk fetch-provider soname:NAME ARCH --output NEW_DIRECTORY [--cata-
log DIRECTORY | --source SOURCE --repo REPO --root DIRECTORY] [--sha256
HASH] [--ca-file FILE] [--public-key FILE]
holypkg apk fetch NAME VERSION ARCH --output NEW_DIRECTORY [--catalog DI-
RECTORY | --source SOURCE --repo REPO --root DIRECTORY] [--sha256 HASH]
[--ca-file FILE] [--public-key FILE] [--import] [--require-soname SONAME]
[--require-file /PATH]
holypkg xbps index REPODATA --sha256 HASH --source NAME --base HTTPS_BASE/
--output NEW_DIRECTORY [--public-key FILE]
holypkg xbps sync HTTPS_BASE/ ARCH --sha256 HASH --source NAME --output
NEW_DIRECTORY [--ca-file FILE] [--public-key FILE]
holypkg xbps sync-source ALIAS ARCH --root DIRECTORY --sha256 HASH --output
NEW_DIRECTORY [--ca-file FILE] [--public-key FILE]
holypkg xbps search|info QUERY --catalog DIRECTORY [--source ALIAS --root
DIRECTORY]
holypkg xbps search|info QUERY --source ALIAS --index-arch ARCH --root DI-
RECTORY
holypkg xbps providers SONAME --catalog DIRECTORY
holypkg xbps providers SONAME --source ALIAS --index-arch ARCH --root DI-
RECTORY
holypkg xbps fetch NAME VERSION ARCH --catalog DIRECTORY --output NEW_DI-
RECTORY [--ca-file FILE] [--public-key FILE] [--source ALIAS --root DIREC-
TORY] [--import] [--require-soname SONAME]
holypkg xbps fetch NAME VERSION ARCH --source ALIAS --index-arch ARCH
--root DIRECTORY --output NEW_DIRECTORY [--ca-file FILE] [--public-key
FILE] [--import] [--require-soname SONAME]
holypkg repo mirror HTTPS_BASE/ --sha256 INDEX_SHA256 --output NEW_DIREC-
TORY [--public-key PUBLIC_KEY.pem] [--ca-file FILE]
holypkg repo seal DIRECTORY [--key PRIVATE_KEY.pem]
holypkg repo verify DIRECTORY --key PUBLIC_KEY.pem
holypkg source plan --config FILE --root DIRECTORY
holypkg source apply PLAN --sha256 HASH --root DIRECTORY
holypkg source list --root DIRECTORY
holypkg source show ALIAS --root DIRECTORY
holypkg source catalog bind ALIAS MIRROR [--root DIRECTORY]
holypkg sync SOURCE [--root DIRECTORY] [--output NEW_DIRECTORY] [--sha256
INDEX_SHA256 | --accept-unsigned INDEX_SHA256] [--commit GIT_COMMIT] [--ca-
file FILE]
holypkg sync APK_SOURCE [--repo REPO] --output NEW_DIRECTORY [--root DIREC-
TORY] [--sha256 HASH | --accept-unsigned HASH] [--ca-file FILE] [--public-
key FILE]
holypkg sync XBPS_SOURCE --arch ARCH --sha256 HASH --output NEW_DIRECTORY
[--root DIRECTORY] [--ca-file FILE] [--public-key FILE]
holypkg sync APT_SOURCE --suite SUITE --component COMPONENT --index-arch
ARCH --keyring FILE --output NEW_DIRECTORY [--root DIRECTORY] [--inrelease]
[--files] [--ca-file FILE]
holypkg orphan [--root DIRECTORY] [--json]
holypkg conflict [--root DIRECTORY] [--json]
holypkg index [--root DIRECTORY] [--path PATH | --capability KIND --name
NAME] [--json]
holypkg run SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC]
[--auto-view] [--view PUBLIC=PRIVATE ...] -- COMMAND [ARGS...]
holypkg docs --root DIRECTORY --output FILE
holypkg config check FILE
holypkg info local:FILE
holypkg info SOURCE:PACKAGE [--repo REPO] [--arch ARCH] [--suite SUITE
--component COMPONENT --index-arch ARCH] [--catalog MIRROR] [--root DIREC-
TORY]
holypkg search QUERY [--source SOURCE] [--repo REPO] [--arch ARCH] [--suite
SUITE --component COMPONENT --index-arch ARCH] [--file] [--fuzzy] [--cata-
log MIRROR] [--root DIRECTORY]
holypkg verify local:FILE
holypkg manifest local:FILE
holypkg requirements local:FILE
holypkg requirements local:FILE --json
holypkg provides local:FILE
holypkg provides local:FILE --json
holypkg solve local:ROOT [local:CANDIDATE ...]
holypkg solve local:ROOT [local:CANDIDATE ...] --choose REQUIRE-
MENT_ID=SHA256 [--json]
holypkg solve local:ROOT [local:CANDIDATE ...] --json
holypkg fetch local:FILE --output DIRECTORY
holypkg fetch local:FILE --extract --output DIRECTORY
holypkg fetch SOURCE:PACKAGE [--catalog MIRROR] --output DIRECTORY [--ex-
tract] [--root DIRECTORY]
holypkg fetch APK_SOURCE:PACKAGE --version VERSION --arch ARCH [--repo
REPO] --output NEW_DIRECTORY [--catalog DIRECTORY] [--root DIRECTORY]
[--ca-file FILE] [--public-key FILE] [--sha256 HASH] [--import] [--require-
soname SONAME] [--require-file /PATH]
holypkg fetch XBPS_SOURCE:PACKAGE --version VERSION --arch ARCH --output
NEW_DIRECTORY [--catalog DIRECTORY] [--root DIRECTORY] [--ca-file FILE]
[--public-key FILE] [--import] [--require-soname SONAME]
holypkg fetch APT_SOURCE:PACKAGE --version VERSION --arch ARCH --suite
SUITE --component COMPONENT --index-arch ARCH --output NEW_DIRECTORY
[--catalog DIRECTORY] [--root DIRECTORY] [--ca-file FILE] [--import] [--re-
quire-file /PATH]
holypkg fetch https://URL --sha256 SHA256 --output DIRECTORY [--ca-file
FILE]
holypkg pack DIRECTORY --output FILE.holy
holypkg manifest generate DIRECTORY --output FILE
holypkg split TREE --output NEW_FILE [--split OUTPUT GLOB ...] [--debug]
holypkg elf FILE [--build-id]
holypkg check local:FILE --root DIRECTORY
holypkg check local:FILE --root DIRECTORY --json
holypkg check [SOURCE:PACKAGE] [--root DIRECTORY] [--arch ARCH] [--libc
LIBC] [--json]
holypkg files SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC]
holypkg why SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC]
[--json]
holypkg owner PATH [--root DIRECTORY]
holypkg repair SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc
LIBC] [--plan PLAN_SHA256]
holypkg rm SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC]
[--yes] [--accept-broken]
holypkg elf FILE
holypkg scan local:FILE
holypkg preview local:FILE --root DIRECTORY
holypkg preview local:FILE --root DIRECTORY --json
holypkg cache stage local:FILE --root DIRECTORY
holypkg cache verify SHA256 --root DIRECTORY
holypkg cache list --root DIRECTORY
holypkg cache clean SHA256 --root DIRECTORY [--yes [--accept-unavailable]]
holypkg db init --root DIRECTORY
holypkg db status --root DIRECTORY
holypkg db status --root DIRECTORY --json
holypkg db reserve SHA256 --root DIRECTORY
holypkg db cancel --root DIRECTORY
holypkg db recover --root DIRECTORY
holypkg db recover --abort-empty --root DIRECTORY
holypkg db recover --continue --root DIRECTORY
holypkg db recover --finish-apply --root DIRECTORY
holypkg db preflight --root DIRECTORY
holypkg db preflight --root DIRECTORY --json
holypkg db plan --root DIRECTORY
holypkg db approve PLAN_SHA256 --root DIRECTORY
holypkg db recheck --root DIRECTORY
holypkg db apply --root DIRECTORY
holypkg db plan-set ROOT_SHA256 [CANDIDATE_SHA256...] [--choose ID=SHA256]
[--source ARTIFACT=SOURCE_ID ...] [--accept-arch SHA256 ...] [--accept-
privileged SHA256 ...] [--skip-hooks SHA256 ...] --root DIRECTORY
holypkg db apply-set PLAN_SHA256 ROOT_SHA256 [CANDIDATE_SHA256...]
[--choose ID=SHA256] [--source ARTIFACT=SOURCE_ID ...] [--accept-arch
SHA256 ...] [--accept-privileged SHA256 ...] [--skip-hooks SHA256 ...]
--root DIRECTORY
holypkg db recover --finish-set|--continue-set --root DIRECTORY
holypkg db configure-plan SHA256 --root DIRECTORY
holypkg db configure-apply PLAN_SHA256 SHA256 --root DIRECTORY
holypkg db configure-recover SHA256 --retry --root DIRECTORY
holypkg db repair-plan SHA256 --root DIRECTORY
holypkg db plan-update OLD_SHA256 NEW_SHA256 [--accept-arch NEW_SHA256]
[--accept-privileged NEW_SHA256] --root DIRECTORY
holypkg db apply-update PLAN_SHA256 OLD_SHA256 NEW_SHA256 [--accept-arch
NEW_SHA256] [--accept-privileged NEW_SHA256] --root DIRECTORY
holypkg rollback TRANSACTION [--root DIRECTORY] [--apply PLAN_SHA256]
[--accept-arch ARTIFACT_SHA256] [--accept-privileged ARTIFACT_SHA256]
holypkg db recover --update --root DIRECTORY
holypkg db repair SHA256 --plan PLAN_SHA256 --root DIRECTORY
holypkg db recover --repair --root DIRECTORY
holypkg db check SHA256 --root DIRECTORY
holypkg db check --all --root DIRECTORY
holypkg db check SHA256|--all --root DIRECTORY --json
holypkg db rm SHA256 [--accept-broken] --root DIRECTORY
holypkg db owner PATH --root DIRECTORY
holypkg repo index DIRECTORY
holypkg repo list DIRECTORY
holypkg repo requirements DIRECTORY NAME
holypkg repo search DIRECTORY NAME
holypkg repo search DIRECTORY NAME --fuzzy
holypkg repo search-file DIRECTORY ABSOLUTE_PATH
holypkg repo search-file DIRECTORY NAME_OR_PATH --fuzzy
holypkg repo solve DIRECTORY NAME [--json]
holypkg repo solve DIRECTORY NAME --choose REQUIREMENT_ID=SHA256 [--json]
holypkg repo providers DIRECTORY KIND NAME
holypkg repo providers DIRECTORY KIND NAME --json
holypkg repo seal DIRECTORY
holypkg repo fetch DIRECTORY SHA256 --output DIRECTORY
DESCRIPTION
Local pacman, RPM, Debian, Slackware, APK and XBPS binary import
import snapshots a local pacman tar archive, RPM v4 or v6 package, Debian
.deb, Slackware package, APK v2 or XBPS package, retains its exact bytes as
NEW_DIRECTORY/original and creates verified LZ4-frame .holy outputs. The
output directory must not exist. --format selects the adapter explicitly;
--source records provenance only and grants no registered source identity
or trust. The command does not install files or execute package code.
Without an explicit APK public key, each output records verification=unver-
ified. An explicit key verifies the APK signature but does not grant a reg-
istered source identity.
--format rpm uses librpm to read the header and payload of v4 and v6 pack-
ages. It verifies each supported file digest before packaging with
libarchive. The importer checks header and payload file lists, records
config flags and original provenance, and splits observed ELF ABI groups.
The resolver compares RPM epoch, version and release with a dedicated lib-
solv RPM comparator. Ordinary versioned Requires and Provides use that com-
parator. Automatic self config-file and rpmlib requirements remain in ori-
gin rather than becoming runtime dependencies. Conflicts, Obsoletes,
scriptlets, triggers and file capabilities require further conversion sup-
port. Local import does not verify RPM signatures. Builds without librpm
return status 6 for this format.
build compiles a local holy-recipe(5) manifest. It fetches every declared
source under its pinned digest, unpacks archives into HOLY_SRC, runs the
reviewed phase steps with absolute HOLY_* paths, and packs one .holy per
declared output. Every output is grouped by the ABI facts of the files it
receives, so one build can produce a noarch nolibc document output and sep-
arate ABI libraries. Requirements come from declared depend records and
from the DT_NEEDED entries of the built payload; provides come from the
payload SONAMEs. The result is an ordinary artifact that a later transac-
tion installs. A split-step output receives its own staging tree under
HOLY_SPLIT_DEST, so a payload requirement and a hook script are written
only into the output that carries their file. An unpack step runs after the
manager has extracted every source, so it may move or reshape that content.
See holy-recipe(5) for the manifest, phases, split rules and build environ-
ments.
rpm index reads a local repomd.xml and its primary archive after checking
the explicit SHA-256 of repomd.xml. The parser accepts only the primary
data type, one sha256 checksum and one relative location. It records pack-
age name, epoch, version, release, architecture, archive SHA-256, size and
repository href. rpm sync retrieves repomd.xml and the primary archive
over HTTPS with the same required hash, then converts them. The catalog
stores that normalized row set. The converter also builds a digest-checked
capabilities index from the primary.xml rpm:provides entries. File-index
coverage is unavailable; dependency coverage stays partial because file-
based requirements need payload scan. Search and info read the catalog af-
ter rechecking the repomd, primary, catalog and capabilities digests. rpm
providers CAPABILITY returns exact, source-attributed candidate hints with-
out downloading every archive. The claim remains unverified until the se-
lected payload is imported and its ELF files are checked. An absent match
means no declared hint in this index, not proof that the source lacks the
capability. rpm fetch selects an exact name/EVR/architecture, downloads the
archive, checks size and SHA-256, and can import it with the same converter
as --format rpm. The standalone index/sync source name records provenance
but does not register a source-id.
A registered RPM-MD source uses type rpm-md and one url HTTPS_BASE/.
holypkg sync RPM_MD_SOURCE requires an explicit repomd --sha256 pin,
records the registry source-id in the conversion record and binds the cata-
log under the target database. The common fetch RPM_MD_SOURCE:PACKAGE com-
mand takes --version and --arch, checks the bound catalog and the current
registry, and downloads through the same verifier. A missing version or ar-
chitecture returns decision-required (3). An explicit --catalog must re-
solve to the bound catalog directory. Changing the registered url leaves
the earlier binding invalid, so search and fetch report coverage unavail-
able until a new sync succeeds. No repository signature is checked: verifi-
cation stays hash-pinned, and trust require needs a key this backend does
not implement.
--format deb reads the Debian ar members debian-binary, control.tar and
data.tar in order, including supported compressed variants. It retains con-
trol files beneath HOLY/foreign/deb and each control field with its source
line in HOLY/origin. Debian maintainer scripts remain review-required and
never run during import. Debian conffiles paths must name regular payload
files. The importer records them with the config manifest flag and rejects
missing, duplicate or unsafe paths. Modified installed config files report
changed-config. Cached updates preserve a modified config when both pack-
age versions mark it config. Comma-separated Depends entries become pack-
age requirements. OR alternatives remain one requirement with version con-
straints on each branch. The solver records the selected branch and
provider in the installed graph. Constraints use Debian ordering, including
epoch, tilde and revision, only against providers in the deb version fam-
ily. Architecture qualifiers, Pre-Depends and other unsupported relation-
ships remain attributed foreign requirements that require an explicit deci-
sion in the current solver. The automatic cross-source provider search
checks every branch of a missing OR group. It stages indexed candidates
from the chosen source; the solver then checks each branch's version and
requests a choice if several providers remain valid. Simple Provides names
and exact version claims become package capabilities; an unversioned vir-
tual claim does not satisfy a versioned dependency. Unsupported or dupli-
cate claims remain foreign requirements. The converter supports Architec-
ture all, amd64 and i386; it checks observed ELF machines against that
claim and returns status 3 for unknown or mismatched payloads. It does not
verify Debian signatures. When control/md5sums is present, the importer
verifies each listed regular payload file before writing native outputs.
MD5 detects payload mismatch here; it does not authenticate the source
archive. make check-deb exercises valid and malformed ar members, codecs,
dependencies, inactive hooks and native install/check/remove in a temporary
root. The Debian reader accepts a numeric 2.x debian-binary version with
following version-file lines, ignores additional ar members after data.tar,
and checks that the control/data compression matches its member suffix. The
version file is limited to 4096 bytes.
apt index accepts a local Debian Packages index with an explicit SHA-256
pin. It keeps the compressed source bytes in original and parses Package,
Version, Architecture, Filename, Size, SHA256, Depends, Pre-Depends and
Provides. Duplicate package/version/architecture entries, unsafe paths,
missing hashes, empty indexes and malformed stanzas fail conversion. The
catalog records its source name, HTTPS base URL, pin and package count. The
source name is provenance, not a registered source-id. The output directory
must not exist. apt sync downloads Packages over HTTPS into private stag-
ing, checks its explicit SHA-256 pin and creates the same local catalog. It
does not authenticate the Debian Release/InRelease signature. apt sync-
signed downloads dists/SUITE/Release, Release.gpg and COMPONENT/binary-
ARCH/Packages.gz over HTTPS. It runs gpgv with the selected keyring, checks
the Suite or Codename, any Valid-Until deadline, and the signed SHA256 and
size of Packages.gz. It stages the catalog beside the output and publishes
it only after all checks pass. The catalog keeps Release, Release.gpg, the
selected keyring and a proof record. Search, info and fetch recheck that
proof; deleting it makes the signed catalog invalid. gpgv is an external
requirement for this mode and the selected keyring is trusted for this op-
eration. The command does not register that keyring as a source-id or im-
plement the full APT repository policy. Signed catalog fetch receipts use
verification=release-gpgv-user-key. Missing Valid-Until is accepted. --in-
release selects dists/SUITE/InRelease instead of Release and Release.gpg.
gpgv checks the signature; a strict clearsign reader extracts the signed
text. Only that text supplies the index hash and suite policy. The catalog
keeps InRelease, the extracted Release, the keyring and a proof record.
Queries recheck the signature. Fetch receipts use verification=inrelease-
gpgv-user-key. Selection is explicit; a failed InRelease fetch or signature
does not fall back to detached Release.gpg. --files also downloads COMPO-
NENT/Contents-ARCH.gz. Sync checks its signed Release SHA-256 and size,
gzip stream and expanded size before publication. The catalog records the
compressed file and its proof. Catalog reads recheck the file hash, re-
trieval time and partial coverage status. A missing signed Contents entry
fails this requested sync. apt sync-source reads an active type apt source
from the target-root registry. Its public-key is a binary OpenPGP keyring
whose SHA-256 is frozen by source plan. The supplied --keyring must match
that fingerprint. Sync checks the source again before publication, records
its stable source-id and writes a binding in the target database. apt bind
can replace a binding for a previously verified catalog. The binding keys
source-id, suite, component and index architecture. Search, info and fetch
resolve these coordinates without --catalog. Each operation checks the
stored catalog path, index, Release proof and current source URL/key.
Alias renames preserve the binding; a changed source URL or key invalidates
it. With an explicit --catalog, --source and --root check the catalog
against the registered source without changing its binding. The package ar-
chitecture in apt fetch is distinct from --index-arch.
The common sync APT_SOURCE command uses the signed Release verifier and
binds its result. It requires suite, component, index architecture, keyring
and output. --inrelease selects a clearsigned Release; --files includes the
signed Contents index. Common search, info and fetch require the same three
catalog coordinates and verify the binding. Common fetch also requires the
package version and architecture; --import and --require-file keep the APT
payload checks. A supplied --catalog must resolve to the bound directory.
apt search and apt info read the catalog and recheck its original index di-
gest. Search matches package name substrings; info requires an exact name
and returns decision-required if several versions or architectures match.
apt search /PATH --file looks up an exact file path in the verified Con-
tents index and prints matching indexed package names. Without --files cov-
erage is unavailable and status 6 is returned. An absent match in partial
coverage is unknown and also returns 6. Contents is a source hint; the im-
ported package payload must still be checked before a file requirement is
satisfied. apt fetch selects an exact name, version and Debian architec-
ture, downloads the listed Filename over HTTPS, and checks its SHA-256 and
Size. The output retains the original .deb and a selection receipt. With
--import, it invokes the local Debian converter and checks the native out-
put identity. The converter checks the downloaded DEB hash after staging,
then records the selected index hash, source URL and verification result in
each .holy origin. Signed catalogs also record keyring and Release signa-
ture hashes. Direct local DEB import remains unverified. Index and selec-
tion receipts say pinned-unverified when only an external pin is available.
The signed path uses the separately selected keyring and Release.gpg. Reg-
istered operations include a checked source-id in their selection receipt.
With --import --require-file /PATH, fetch checks that Contents names the
selected package, then verifies the exact nondirectory path in the imported
receipt. The option requires --import. It does not install packages. make
check-apt covers local and HTTPS indexes, tampering, TLS fetch, hash mis-
match and import identity.
--format slackware reads a .txz, .tgz, .tbz or .tlz tar package and checks
its compression against the filename suffix. It parses the name-version-
arch-build filename from the right, preserves the build tag as the native
release, and retains install/ files under HOLY/foreign/slackware. in-
stall/doinst.sh is recorded as a review-required shell hook and is not run
during conversion. Unknown install/ control files become attributed foreign
requirements. Official Slackware packages need not declare dependencies;
the importer does not invent them. It accepts noarch, x86_64 and
i386/i486/i586/i686 source architecture labels, then separates observed ELF
arch/libc outputs. An unknown or contradictory claim returns status 3. The
Slackware version family has no automatic comparator yet, so update choice
requires review. make check-slackware covers codecs, links, scripts, path
rejection, mixed ELF outputs and install/check/remove of a converted data
package.
--format apk reads two or three concatenated gzip members from an APK v2
package. It retains .PKGINFO, optional signatures and control scripts be-
neath HOLY/foreign/apk. When .PKGINFO contains datahash, the importer
checks its SHA-256 against the compressed data member before creating out-
puts. With --public-key FILE, the importer snapshots an RSA PEM key, re-
quires datahash and a signature member, and verifies the signature over the
compressed control member. The key filename must match the .SIGN key name.
The conversion receipt and each output origin record the algorithm and pub-
lic-key SHA-256. The caller must establish the key's authenticity outside
the import command. Unversioned package, so:SONAME and cmd:COMMAND depen-
dencies become exact requirements. Package constraints using the supported
APK v2 number, letter, suffix and revision grammar use APK version ordering
only against APK-family providers. Unknown version forms, namespaced ver-
sioned requirements and other unsupported tokens retain their original ex-
pressions as foreign requirements. APK virtual provides, install-if and re-
places also require review because their selection and file-ownership se-
mantics differ from Holy's simple capabilities. Scripts remain review-re-
quired and do not run during import. The importer accepts noarch, x86 and
x86_64 claims and checks observed ELF machines. It rejects truncated gzip
members, extra members and unsafe paths. make check-apk covers these cases
and installs/checks/removes a converted data package in a temporary root.
make check-apk-version covers comparisons against Alpine apk-tools 2.14.12.
--format xbps reads a Void XBPS tar package, parses props.plist and
files.plist with libplist, and checks payload file size and SHA-256 against
the file list. It compares symlink targets by their resolved path inside
the target root; a relative archive target and the equivalent absolute
files.plist target match. It preserves the original property lists beneath
HOLY/foreign/xbps and records unverified provenance for a direct local im-
port. Package revision becomes the shlib-requires become arch/libc-scoped
SONAME requirements. Simple run_depends of the form name<version,
name<=version, name>version or name>=version become package requirements.
Holy compares their versions only against XBPS-family providers with Dewey
component order and the _N revision. Complex ranges, globs, exact package
strings and other XBPS-specific relations remain attributed foreign re-
quirements. Provides are preserved in origin metadata without creating de-
pendencies. INSTALL and REMOVE scripts remain review-required. Unknown
file-list fields, unsupported architecture, and ELF/architecture contradic-
tions stop conversion. make check-xbps-import covers a generated package,
ELF-derived SONAME capabilities, dependency selection and malformed file
lists; make check-xbps-version checks component and revision ordering.
These do not test the full Void package range.
xbps index reads a local $ARCH-repodata archive after checking its explicit
SHA-256. xbps sync retrieves the named architecture's repodata over HTTPS
with the same required hash. The catalog stores package name, version, ar-
chitecture, archive SHA-256 and size. Search and info read the catalog af-
ter checking its digest and the retained repodata. File-index coverage is
marked unavailable. The converter also builds a digest-checked capabilities
index from XBPS shlib-provides. xbps providers SONAME returns exact,
source-attributed candidate hints without downloading every archive. The
claim remains unverified until the selected payload is imported and its ELF
files are checked. An absent match means no declared hint in this index,
not proof that the source lacks the library. The standalone index/sync
source name records provenance but does not register a source-id. sync-
source reads an active type xbps source from the target-root registry,
checks its URL and selected key, and records its source-id. Source-aware
search/info/fetch check that source-id and the URL against the current reg-
istry. sync-source binds the verified conversion digest and catalog path
under the target database lock. Search, info and fetch can select that
binding by source alias and index architecture; changed conversion records
or catalog bytes fail validation. Bindings survive alias renames that re-
tain the same source-id. A catalog inside the target root is stored as a
root-relative path, so moving that root with its cache preserves the bind-
ing. With --public-key FILE, index requires that the supplied RSA public
key match index-meta.plist and records its SHA-256 fingerprint. The caller
must establish the key's authenticity independently. The repodata still
requires an explicit SHA-256 pin; index-meta.plist alone is not trusted au-
thority for a new key.
The common sync XBPS_SOURCE command takes --arch and an explicit repodata
--sha256. It verifies the registered source and key, then binds the cata-
log. The common fetch XBPS_SOURCE:PACKAGE command takes --version and
--arch, checks the bound catalog and registered key, and uses the same
archive verifier and optional import as xbps fetch. A missing version or
architecture returns decision-required (3). An explicit --catalog must re-
solve to the bound catalog directory; a copied unbound catalog is rejected.
xbps fetch selects an exact name/version/arch from that catalog, downloads
the archive over HTTPS, checks size and SHA-256, and compares props.plist
identity with the selected record. With --public-key FILE, fetch also re-
quires the index's key fingerprint, retrieves the .sig2 sidecar and checks
its RSA SHA-256 signature over the archive bytes. The receipt distinguishes
hash-pinned from rsa-sha256. With --import, fetch converts the verified
archive into .holy outputs under NEW_DIRECTORY/converted and checks their
identity. The converter checks the archive hash again after staging and
records hash-pinned or rsa-sha256 in each output origin. A signed output
also records the public key and signature SHA-256. Fetched outputs retain
the selected repodata SHA-256 and credential-free source URL. A direct im-
port remains unverified and has no asserted repository generation. --re-
quire-soname requires --import and checks exact SONAME claims from the con-
verted ELF payload; an index hint alone does not satisfy it. The complete
fetch receipt records source-id for a checked source binding, whether im-
port ran, and any verified SONAME. Import failure leaves the archive and
incomplete output without a complete fetch receipt. Fetch does not install
or execute the package. The current transport limits one archive to 1 GiB;
larger indexed objects return status 6. An explicit import of the fetched
digest-named file creates .holy outputs. make check-xbps-index covers in-
dex parsing, HTTPS sync/fetch, registered source checks, RSA signature ver-
ification, conversion and failure cases on a local fixture server. Key en-
rollment and automatic provider discovery remain open.
The apk index command snapshots a local APKINDEX.tar.gz, separates its gzip
signature and data members, and publishes a sorted local catalog with the
original bytes, source alias, base URL and SHA-256. It checks required
package identity fields and duplicate name/version/arch slots. The source
alias is provenance, not a registered source-id. A publisher signature is
retained in the original input; direct apk index conversion records unveri-
fied status. Package coverage is complete for the supplied index, while
file coverage is unavailable. apk search matches a package-name substring
and apk info selects an exact name; multiple versions require a decision.
Queries verify the catalog hash recorded at conversion. apk fetch obtains
the indexed APK over HTTPS, checks its recorded size and Q1 SHA-1 com-
pressed-control checksum, then compares .PKGINFO identity and optional
datahash with the compressed data member. --sha256 adds a full artifact di-
gest supplied by the caller. The output contains the original APK and a se-
lection receipt with index/catalog hashes. For a bound source with a con-
figured RSA key, apk fetch requires --public-key, checks its fingerprint
against the registered key, and verifies the APK signature over the com-
pressed control member. The receipt records index and package verification
separately. Direct fetch without a bound keyed source records the package
signature as unverified. apk sync reads an active type apk source and
named repo from the registered source definitions. It fetches AP-
KINDEX.tar.gz over HTTPS. A configured RSA public-key freezes the key fin-
gerprint in the source registry. Sync requires the matching --public-key
file and verifies the signed compressed index member using RSA/SHA-1,
RSA/SHA-256 or RSA/SHA-512 according to the .SIGN filename. The public-key
filename must match the signature key name. trust require needs this key
and a valid signature. Without a configured key, --sha256 pins index bytes;
--accept-unsigned confirms the downloaded digest after a decision-required
response. trust ignore permits an unpinned index. Its catalog records
source-id and repo. apk fetch --root requires that exact catalog to be
bound, checks its source-id, alias, repo and URL against the active defini-
tion, and checks again before publishing the package. If the alias changed
while the source-id stayed the same, --source names the current alias for
this check. apk sync also records an atomic catalog binding under the tar-
get database. apk bind replaces that binding for a previously verified
catalog. A source with a configured key must supply it through --public-key
so bind can verify the original index signature again; the binding records
the verification and key fingerprint. Without a configured key, trust warn
requires --accept-unsigned with the exact index digest. Search, info and
fetch can use the bound catalog by source and repo. Readers compare its
saved index and catalog hashes, and reject changed source definitions. A
catalog inside the target root uses a root-relative binding so the root can
be moved.
The common sync APK_SOURCE command calls the same APK verifier and binds
its result. A single configured repository is selected when --repo is ab-
sent; multiple repositories require --repo and return decision-required
(3). APK sync requires --output. --commit applies only to holy-git sources.
Direct fetch without --root records source-binding unchecked. Fetch and
sync do not install files. apk providers soname:NAME searches exact so:NAME
entries in the hash-bound SONAME index generated from APKINDEX. Each result
is an index hint. Use fetch --import --require-soname NAME to verify the
actual ELF before selecting a provider. fetch-provider soname:NAME ARCH
chooses the sole indexed candidate for that architecture, fetches and im-
ports it, then checks the exact ELF SONAME. Multiple candidates return de-
cision-required before download; use apk fetch with an explicit name and
version to choose one. A false index claim fails after import and leaves no
complete selection receipt. Source-bound fetch still checks the registered
source, key and index generation. With --import, fetch converts the
checked archive into .holy outputs under NEW_DIRECTORY/converted, checks
each output's name, version and architecture, and rechecks the original
SHA-256 after staging. Signed packages are verified again during import.
The output origin records the selected index hash and fetched artifact URL;
its verification field describes the package signature, not the index sig-
nature. --require-soname requires --import and checks exact SONAME claims
derived from the imported ELF payload. An index claim alone cannot satisfy
it. --require-file checks one exact nondirectory payload path from the con-
verted manifest. Both requirements need --import and can be combined. Im-
port failure leaves the original and an incomplete output without a com-
plete selection receipt. The APK binding is separate from the native cata-
log and general dependency resolver. make check-apk-index covers signed and
unsigned fixtures, duplicate entries, truncated gzip, traversal and catalog
tampering. make check-apk-fetch covers HTTPS, full digest, size, control
checksum and package signatures. The verified index identifies packages
through APK v2 Q1 SHA-1 checksums. For a package selected from a verified
index, fetch requires keyed source also requires the package signature and
matching public-key filename.
The importer reads .PKGINFO identities, full epoch/version/release strings,
package relations and original field values. It preserves .PKGINFO, .BUILD-
INFO, .MTREE, .INSTALL and .CHANGELOG beneath HOLY/foreign/pacman. Numeric
and symbolic ownership, modes, symlinks and supported hardlinks survive
conversion. The native installer still requires supported ownership and
object semantics.
A single observed ELF ABI receives common data in the same output. Mixed
ELF architectures or libc classes receive separate outputs plus a
noarch/nolibc common-data output. Source architecture remains x-source-
arch; observed payload facts determine output tags. Unknown ELF, unclassi-
fied executable formats, static library archives and pacman backup paths
require a decision, status 3. Source packages require a recipe importer
and are not treated as binary packages.
Package dependencies retain constraints and original expressions. Pacman
package version constraints use the comparator described below. Unsupported
SONAME relations, conflicts, replacements and unknown metadata fields be-
come foreign requirements; the current solver reports decision-required for
a selected output that carries them. Optional and build requirements re-
main in attributed origin records. An .INSTALL script remains review-re-
quired. The current installer refuses its nonempty hooks record. Split out-
puts also require scoped dependency and resolved requirements before in-
stallation. HOLY/transform records changes already made to the packaged
payload; the installer does not execute it. Conversion success alone does
not establish that an application can be installed or launched.
The conversion receipt binds the original SHA-256 to every output filename,
SHA-256 and arch/libc pair. The importer publishes it only after completing
all outputs. A failed conversion retains the original and any completed
outputs; without a complete receipt the output set is incomplete. Existing
output files are not overwritten. Origin metadata records the format-spe-
cific converter identity; executable build attestation and publisher veri-
fication remain unimplemented.
Libarchive supplies tar and built-in gzip, bzip2, XZ, Zstandard and LZ4
codecs when available in the selected build. APK v2 member boundaries use
zlib. A missing codec returns status 6 and does not invoke an external de-
coder. The static dependency profile builds all five codecs with musl and
rejects a libarchive configuration missing any of them. Import uses anony-
mous spool files instead of extracting foreign paths on the host. It re-
jects duplicate paths, unsafe links, non-directory ancestors, unsupported
special nodes, ACLs and xattrs. Limits are 1 GiB compressed input, 1 GiB
per entry, 4 GiB payload spool, 100000 entries and 1 MiB .PKGINFO or Debian
control. Debian control.tar also has a 16 MiB outer member limit; the
data.tar.lzma legacy variant uses libarchive's LZMA filter when available.
The native writer verifies outputs through /proc/self/fd before publica-
tion.
make check-pacman runs parser and archive fixtures, including a retained
.PKGINFO from Arch gzip 1.15-1, malformed archives, hooks that must not ex-
ecute, mixed ELF outputs and installation of a converted data package in a
temporary root. make check-static-import STATIC_HOLYPKG=FILE verifies the
six tar/codec paths, including truncated compressed inputs, in a private
mount/PID namespace and a chroot containing the client, inputs, /tmp and a
private /proc. It installs, checks and removes each converted fixture with-
out dynamic libc or decoder executables. The test requires doas, unshare,
mount, chroot, Python, readelf and host zstd/lz4 commands to create input
fixtures. These host compressors are not available inside the chroot.
make check-pacman also runs 92 attributed version comparisons from pacman
7.1.0, plus scoped candidate selection, mixed-family rejection, installa-
tion and rejection of updates that violate an installed consumer's con-
straint.
The reference metadata format is https://alpm.archlinux.page/specifica-
tions/PKGINFO.5.html.
Pinned HTTPS catalog mirrors
repo mirror downloads one explicit catalog generation into a new local di-
rectory. HTTPS_BASE must end with slash and contain no credentials, query
or fragment. The command requests index.INDEX_SHA256, checks its digest,
parses the entire supported holy-index-prototype-1/2 catalog, and downloads
its packages by their recorded hashes. It URL-escapes each filename as one
path component. The common HTTPS transport validates certificates, permits
at most five redirects and refuses credential-bearing redirect targets.
--ca-file selects a fixture or private repository CA.
The mirror verifies every archive, payload, known ABI, dependency record,
identity, size and indexed capability claim before sealing its local cur-
rent pointer. The final sealed index must still match INDEX_SHA256. A trun-
cated, duplicate, forged or unavailable object cannot publish a usable cat-
alog. Index transfers are limited to 16 MiB and package transfers to 1
GiB, each with the common five-minute deadline including redirects. This
operation downloads the entire selected catalog, rather than resolving a
single package.
The new directory contains the original filenames, index, index.DIGEST,
current and mirror-origin. The latter records the base URL, pinned digest
and digest-pinned-unsigned verification status. repo list/search/providers,
repo solve and repo fetch operate on the resulting local snapshot without
network access. A valid empty catalog is supported. Existing output direc-
tories are refused. Download or validation errors leave partial files with-
out a current pointer; a new attempt uses a new output directory. An I/O
failure during final pointer publication can require inspection of current,
as with repo seal. Success prints only the sealed index digest.
The caller supplies the digest. Without --public-key, this command does not
verify a publisher signature. With --public-key, it verifies the signed in-
dex against that PEM key before downloading packages. It does not activate
a configured source, assign source-id or install packages. Configured sync
with a pinned digest is described below. Successful return is 0; invalid
arguments return 2, conflicting hashes or catalog contents 4, unavailable
TLS/network inputs 6, and local filesystem errors 1. The directory remains
private to its owner until that owner chooses to share it.
Installed documentation
holypkg docs --root DIRECTORY --output FILE creates a new holy-docs-1 bun-
dle from the selected root's installed manifests. It holds the database
shared lock, rejects pending transactions and visits artifacts in digest
order. It includes regular man sources beneath usr/share/man, including
localized subdirectories, after comparing each opened file's SHA-256 to its
installed manifest. It refuses symlinked parent directories and reads no
configs, origin URLs, passwords or files outside the installed man list.
Each page records its name, section, path, package name/version, installed
source-id and artifact and source-file hashes. Local delivery has source-id
"-" unless explicitly associated with a registered source. Same-name pages
retain separate attributed records. Sources remain roff text; this command
does not run a formatter or interpret roff directives. It decodes gzip,
bzip2, xz and zstd with built-in libarchive filters when compiled in, and
never invokes a decompression helper. Hardlinked manual names retain sepa-
rate attributed pages after inode-group checks. The compressed source di-
gest identifies the original file. Symlink aliases are checked without fol-
lowing them and listed as references rather than expanded.
missing-man identifies packages with no included regular page. omitted-man
lists unsupported page names/codecs. The final summary records generation,
package/page/alias counts and missing/omitted coverage. A successful bundle
can contain those explicit omissions; inclusion does not assess a manual's
semantic completeness. Sources and manifests are limited to 16 MiB per
file; decoded pages are limited to 64 MiB. Empty or NUL-containing page
bodies fail.
The command builds a temporary file beside FILE and publishes without re-
placing an existing output. Invalid or changed man files return 4; pending
transactions return 5; output and database I/O failures return 1. Failure
leaves existing output untouched. It requires neither root nor network. For
image roots whose manifests use UID 0, run inside the builder's matching
user namespace. HOLY_DOCS_CHROOT=1 sh tests/installed-docs.sh ./holypkg
additionally exercises the static binary and gzip decoding in a libc-free
chroot using host doas.
Bootstrap source retrieval
make fetch-sources INPUTS=DIRECTORY SOURCES="NAME ..." obtains selected
pinned archives from profiles/static-sources. make fetch-bootstrap-sources
uses profiles/bootstrap-sources for BusyBox, dinit, mdevd, skalibs, glibc
and musl.cc cross compilers. Omitting SOURCES selects the whole manifest.
The fetcher accepts HTTPS and HTTPS redirects, verifies SHA-256 before pub-
lishing each file, and checks existing files before reuse. A wrong digest
stops the command without replacing that file. A failed download leaves no
published archive. The resulting INPUTS directory feeds the bootstrap tar-
gets; fetching alone does not install a package.
Bootstrap shell package
The repository target make bootstrap-busybox ARCH=i686|x86_64 INPUTS=DIREC-
TORY OUTPUT=DIRECTORY builds a small BusyBox package with static musl link-
age. The default is x86_64; i686 requires multilib GCC and records package
architecture x86. INPUTS must contain musl-1.2.5.tar.gz and busy-
box-1.37.0.tar.bz2 from their upstream release URLs. KERNEL_HEADERS must
name installed Linux UAPI headers with linux and asm subdirectories. The
builder records a digest of those headers and searches them after the musl
headers when compiling network applets. The build checks pinned SHA-256
digests before unpacking, uses GCC and upstream make builds, and installs
the build toolchain only in a temporary directory. OUTPUT must not exist.
Its absolute path must use ASCII letters, digits, underscore, dot, slash or
hyphen. JOBS controls build parallelism, default 2. The optional GCC flag
-fno-link-libatomic disables the host compiler's automatic libatomic link-
age when that flag is available; the musl link otherwise uses only the se-
lected static libraries.
Outputs include busybox.holy, build.log, build.record, busybox.config, an
applet list, ELF inspection and shell probe records. Source URLs, source
hashes, compiler version, config hash, artifact hash, exit status and
elapsed seconds are recorded. Upstream BusyBox and musl copyright files ac-
company the payload. The package contains /usr/bin/busybox; invoke applets
as busybox APPLET. The configuration is profiles/busybox-bootstrap.config.
It includes ash, basic file utilities, mount tools, ip, udhcpc and
nslookup. The separate static-network fixture verifies interface setup, DNS
and HTTPS with a local CA; the default boot image still has networking dis-
abled. LFS is enabled for musl's 64-bit file offsets on i686 as well as
x86_64. The manifest records the builder's numeric UID/GID. This is a lo-
cal bootstrap experiment, not an official base image or a general recipe
engine.
make check-bootstrap-busybox BUSYBOX_PACKAGE=FILE installs the package in a
disposable root with prepared directories, runs its shell there with doas
and chroot --userspec, checks files and removes the package. Noninteractive
doas must be available. The chroot has no dynamic libc directories. This
fixture approves non-native placement by exact artifact hash. For x86 pack-
ages it also requires qemu-i386 and runs the shell with its pentium2 CPU
model. This tests the static shell; host holypkg still runs outside the ch-
root. Boot, static holypkg and full libc recovery require separate accep-
tance tests.
Host build
The core, the package manager and the installer are C99, and the build is
make with the ordinary CC, CPPFLAGS, CFLAGS, LDFLAGS and LDLIBS. make
CC=tcc, make CC=gcc and make CC=clang each compile it, and make check-cc
proves it: the target compiles the core with tcc, gcc and clang in turn,
packs a package with the result, verifies it and lists its payload, then
restores the default build. A missing toolchain returns 6 instead of a
green run, and a toolchain that breaks the C99 build fails the target with
its own compiler output.
Static core build
make bootstrap-mdevd ARCH=i686|x86_64 INPUTS=DIRECTORY OUTPUT=DIRECTORY
builds a musl-static mdevd 0.1.8.2 package with skalibs 2.15.1.0. The de-
fault is x86_64. STATIC_PREFIX may select a completed make static-deps
toolchain for the same architecture; i686 requires it. The build copies and
hashes its build.record and records the compiler wrapper hash. INPUTS con-
tains mdevd-0.1.8.2.tar.gz and skalibs-2.15.1.0.tar.gz from the upstream
GitHub tag archives, plus x86_64-linux-musl-cross.tgz from musl.cc when
STATIC_PREFIX is omitted. The script checks pinned hashes of private
copies, builds with upstream configure/make, and records compiler version,
both configurations, ELF facts and artifact hash. OUTPUT must be new and
use ASCII letters, digits, underscore, dot, slash or hyphen. JOBS defaults
to 2. Upstream licenses and HTML documentation accompany the package;
docs.record reports missing man pages. No service or mdev.conf is enabled
by this bootstrap build. Execline directives and external command helpers
require additional packages and configuration.
make check-bootstrap-mdevd MDEVD_PACKAGE=FILE installs the artifact into a
disposable root through the reviewed database plan. In a libc-free chroot,
mdevd parses a valid configuration with symbolic root user/group names and
rejects an invalid regular expression with status 2. Host doas, unshare,
chroot and timeout provide a private network namespace and a ten-second
limit. The command runs with the caller's UID/GID; host /dev and /sys are
absent. The fixture then checks and removes the installed package. This
covers installation, static configuration parsing and removal. Uevent han-
dling, coldplug, dinit integration and client libudev compatibility remain
separate acceptance cases.
make bootstrap-dinit ARCH=i686|x86_64 INPUTS=DIRECTORY OUTPUT=DIRECTORY
builds dinit 0.22.1 as a musl-static native package, including its C++ run-
time. The default target is x86_64. Inputs are dinit-0.22.1.tar.gz from the
upstream GitHub tag and the pinned i686-linux-musl-cross.tgz or
x86_64-linux-musl-cross.tgz from musl.cc. The script copies and hashes both
archives before use; an updated archive at the same URL is rejected. It
runs upstream make check, includes upstream man pages and license, and
records compiler version, configuration, ELF facts and package hash. Capa-
bilities support is disabled in this bootstrap profile. This package build
does not establish PID 1 boot acceptance; that requires the image test.
OUTPUT must be new.
make check-bootstrap-dinit DINIT_PACKAGE=FILE BUSYBOX_PACKAGE=FILE stages
both packages and installs them through approved database plans into a dis-
posable chroot without dynamic libc directories. It checks command and man-
page links, static ELF classification, starts a scripted service, queries
its STARTED state through the control socket, requests shutdown and checks
the stop-command effect. The service manager runs with the caller's
UID/GID in user mode. Host doas, chroot, mknod and timeout are required;
the fixture supplies only /dev/null and enforces a 20-second timeout. It
prints the tested package hashes. For i686 it verifies dinit under qemu-
i386 with the pentium2 CPU model. After service shutdown it checks both
installed manifests and removes both packages through the database. This
covers package installation, service control and the static C++ runtime.
PID 1 boot remains a separate test.
make static-deps ARCH=x86_64|i686 INPUTS=DIRECTORY OUTPUT=DIRECTORY KER-
NEL_HEADERS=DIRECTORY builds musl dependencies for the selected target from
the pinned archives listed with URLs and SHA-256 digests in profiles/sta-
tic-sources. It copies and verifies inputs before unpacking. The caller
supplies Linux userspace headers with linux, asm and asm-generic subdirec-
tories. Their installed copies receive a hash inventory. GCC, make, CMake,
autotools, libtool, Perl and archive tools are host build requirements. The
multilib GCC installation must supply libgcc for the selected target. JOBS
controls parallelism, default 2. ARCH defaults to x86_64; i686 uses the x86
package architecture, not the x32 ABI. The compiler wrapper selects the
target machine and linker emulation. The bootstrap compiles and runs a sta-
tic probe, checking ELF class, machine and pointer width before building
dependencies. The builder must execute the target for these configure/probe
steps. Compiler flags and probe evidence are retained.
The output is a new private prefix containing static libarchive/LZ4, zlib,
XZ, Zstandard, bzip2, libelf, libsolv, curl and OpenSSL, plus build compat-
ibility libraries. Its musl toolchain includes a private dynamic loader
for upstream configure probes; the final holypkg link uses -static. No host
libc is replaced. Source manifests, build logs, library hashes, exit sta-
tus and elapsed seconds remain in the prefix. Archive decoding includes
gzip, LZ4, XZ, Zstandard and bzip2; the native writer still emits only LZ4
frames.
make static STATIC_DEPS=DIRECTORY checks the completed dependency prefix,
cleans existing object files and builds holypkg with its musl compiler.
make check-static-target ARCH=i686|x86_64 STATIC_HOLYPKG=FILE MUSL_CC=FILE
checks the static client's ELF class and machine, then runs it under QEMU
user emulation. The selected CPU is pentium2 for i686 or qemu64 for x86_64.
The test builds a static C payload with that compiler and exercises native
packing, installation, check, execution and removal. Its JSON report binds
client and package hashes and records coverage. This is a userspace ISA
test; kernel boot, dynamic libc and physical hardware have separate accep-
tance gates.
make check-static-core STATIC_HOLYPKG=FILE BUSYBOX_PACKAGE=FILE copies that
binary into a disposable root without dynamic libc directories. Inside the
chroot, holypkg verifies and caches the supplied BusyBox package, installs,
checks and removes it twice using the cached artifact. The original package
is removed after caching. A BusyBox shell probe runs after each install.
doas creates the chroot and drops to the caller's numeric UID/GID. For an
ELF32 i686 client, the fixture uses setarch i686 so the process sees the
i686 host personality while the x86_64 kernel executes it. This does not
test booting an i686 kernel. Both artifact hashes are printed on success.
This verifies the local static package path; removal and restoration of
real dynamic libc packages, network recovery and dinit boot still require
their own tests.
Passing DINIT_PACKAGE=FILE to check-static-core also installs, checks and
removes the complete dinit package, including its symlinks, with holypkg
running inside that libc-free chroot. The fixture removes the input after
caching and reports the tested artifact hash. Service control is covered by
check-bootstrap-dinit.
make check-static-network STATIC_HOLYPKG=FILE BUSYBOX_PACKAGE=FILE RE-
PORT=FILE tests that static client in a libc-free chroot with private net-
work namespace, local DNS and HTTPS fixtures, and a generated test CA. It
verifies real DNS queries, certificate rejection, digest rejection, down-
loading and native archive verification. The client drops to the caller's
UID/GID. The fixture creates no external route and supplies no proxy cre-
dentials. Python, OpenSSL, unshare, chroot and noninteractive doas are host
test tools. The packaged static BusyBox ip applet raises the fixture loop-
back from inside the libc-free chroot. Physical network recovery remains a
separate test.
The holy-static-network-test-1 JSON report records architecture, host ker-
nel, input and fixture hashes, CA hash, per-command argv/status/time/logs,
observed DNS query types, completed coverage and unexecuted cases. A dy-
namic binary fails the libc-free probe. REPORT defaults to out/static-net-
work.json. No VM image is involved, so its report field is null. Temporary
keys and rootfs are removed after the test.
Package operations
The config check command checks source and rule configuration without
changing the system. General package installation, foreign import, source
sync and multi-source resolution are not available yet. This executable is
independent of the separate owenewans/holypkg project.
Pinned HTTPS fetch downloads one native .holy into an existing, non-
writable- by-others output directory. It requires an expected SHA-256,
HTTPS including redirects, certificate validation and an URL without embed-
ded user/password. The downloader checks each redirect target before re-
questing it and refuses credentials in redirect URLs. --ca-file selects a
trusted CA file for a private test or source. The command limits the entire
redirect chain to 1 GiB and five minutes, verifies the completed archive,
then publishes SHA256.holy without replacing an existing object. If the
name exists, it compares the stored bytes with the expected digest. HTTP,
unknown certificates, wrong digests and malformed archives cannot publish a
new object. This fetch does not assign source-id, verify a repository sig-
nature, install files or run hooks. General foreign binary fetch remains
separate.
Pack reads a prepared tree containing only HOLY/ and DATA/. HOLY/ must con-
tain exactly meta, files, deps, provides, hooks, origin and transform as
ordinary files. DATA/ may contain directories, ordinary files, symlinks and
direct hardlinks. The writer rejects extended attributes. It records file
modes and numeric ownership, fixes archive timestamps to zero and orders
directory entries by name. It writes a POSIX PAX tar compressed as an LZ4
frame to a temporary file alongside the destination, verifies the completed
artifact, checks known ELF ABI facts and supported dependency/provider
records, and publishes it only when the destination does not exist. Pack
neither generates HOLY/files nor infers ABI facts. A mismatched manifest
fails without publishing a package; recipes and foreign conversion are sep-
arate work.
Manifest generate reads DATA/ below DIRECTORY and writes HOLY/files records
to a new output path outside the input tree. It emits directories and ordi-
nary files, symlinks and hardlinks in sorted traversal order, numeric
UID/GID, modes, sizes, link targets and SHA-256 digests for ordinary files.
Symbolic owner/group names are placeholders; the numeric values remain the
source tree's ownership. The command refuses xattrs, escapes paths with
whitespace or non-ASCII bytes, and does not replace an existing output.
Copy the generated file to HOLY/files and run pack; neither command infers
package arch/libc or dependencies from arbitrary data files. Both commands
inventory regular files by device and inode before writing. For each inode
shared by multiple DATA paths, the bytewise smallest path is the regular
anchor. Other names are direct hardlinks to it, including when tree traver-
sal visits an alias first. The group identifier is the SHA-256 of the an-
chor's archive path, independent of host inode numbers. Links outside DATA
are not archived; a file with only external aliases remains independent.
Changed file identity, attributes or link count during the read causes
failure. The input tree must remain stable throughout generation and pack-
ing. Archive pack and inspection use the active LC_CTYPE locale for
libarchive pathname conversion. Use an available UTF-8 locale for UTF-8
filenames.
Pack and manifest generation read symlink targets without following them.
They reject lexical traversal above the target root; absolute targets refer
to that root. Dangling links are preserved. Symlink xattr inspection re-
quires /proc/self/fd on the build host. Metadata files must remain ordinary
files. These commands do not resolve chains of symlinks or prove runtime
targets exist. The separate fetch --extract operation still refuses ab-
solute symlinks.
The db plan-set command resolves one explicit root against already cached
candidate hashes and cached artifacts from the installed catalog. It sup-
ports exact-name package dependencies and the documented pacman version
constraints between Linux data, native static and restricted dynamic pack-
ages, and nonempty hooks only after an explicit skip decision, existing or
declared safe directories and the same payload subset as db apply. It re-
ports selected artifacts, reasons and requirement edges without changing
installed state. Unused candidates are excluded from the operation. The
plan digest binds target-root device/inode, generation, selected graph, ex-
plicit provider choice and every selected manifest. Inter-package path and
slot conflicts are rejected.
The db apply-set command accepts the reviewed plan digest followed by the
same root and candidates. It holds one writer lock, checks the plan again
and writes transactions/set-journal before the first payload. All selected
packages share one generation increment. Installed state records the ex-
plicit root and dependency reasons; each instance retains the selected
graph. The interface does not execute package hooks. A skipped hook leaves
the package installed-unconfigured. Upgrades and grouped removal are not
implemented by this interface.
Repeated --skip-hooks SHA256 accepts every HOLY/hooks record in the exact
selected artifact without running its scripts. Without the flag, plan-set
returns 3 and prints the hook records for review. The skip decision enters
the plan digest and set journal; apply requires the same flag. Installed
state retains the hooks, transform and a hash-bound hooks-state record.
check reports skipped-hook with status 4. An unrelated artifact cannot con-
sume this decision.
db configure-plan reviews native postinstall records with the syntax hook
postinstall INTERPRETER RELATIVE_SCRIPT_PATH sha256 DIGEST in HOLY/hooks.
The interpreter is an absolute target-root path. The script is a regular
file owned by the installed package and its digest must match. The command
shows each script body, interpreter, root and UID, then prints a plan hash
without running code. The hash also binds the interpreter file bytes. For-
eign-script records and other phases remain unsupported by this command. db
configure-apply requires that hash and root privileges; it runs each re-
viewed script through its interpreter inside a chroot of the selected root
with PATH=/usr/bin:/bin, HOME=/ and LANG=C. Hooks may change files outside
their package manifest. The manager does not claim isolation from arbi-
trary root code.
Before each hook, configure-apply synchronizes a running record in transac-
tions/hook-journal. A successful hook advances the record. A failed hook or
crash leaves status 5 and requires db configure-recover --retry before that
hook runs again. The user must inspect external effects before retrying.
After all hooks succeed, hooks-state becomes completed and the database
generation advances. A pending hook journal blocks other package opera-
tions. The current command handles native postinstall hooks after instal-
lation; preinstall, editing, service consent and automatic configure during
add remain open.
Repeated --accept-privileged SHA256 confirms installation of regular exe-
cutable files with setuid in that exact selected artifact. Without it,
plan-set returns 3 and names the artifact, path and mode. The choice enters
the plan hash, set journal and installed state; apply requires the same
flag. Setgid, sticky bits, setuid directories, symlinks and hardlinks re-
main unsupported. The choice covers only native set transactions, not local
preview, single-package apply or cached update. The caller must review the
full artifact payload and the target root before approving the mode.
Repeated --accept-arch SHA256 confirms placement of selected new artifacts
whose architecture differs from the native host mapping. Each exact hash
needs its own decision. Without it, plan-set returns 3 and reports arti-
fact, host and target. This includes x86 on an x86_64 host when execution
support has not been established. The manager does not infer IA32 support
from uname alone. The flag does not execute payloads, change target meta-
data, waive dependency ABI checks or confirm that the kernel can run the
result. Native/noarch artifacts, duplicate decisions and malformed hashes
are invalid flag uses; unselected or already installed artifacts require a
separate decision.
The plan displays accepted-unverified with artifact scope and hashes the
host/target decision. Apply must receive the same flags. A version-3 set
journal preserves them and the host for recovery, including when only part
of the set was installed. Installed holy-instance-5 state retains the deci-
sion; check reports accepted-arch-mismatch in text or an optional architec-
ture object in JSON, with execution unverified. This annotation does not
turn intact files into a failure. Reused providers retain their existing
decision on that host. It does not authorize a new artifact hash: cached
replacement of an instance carrying this decision currently returns 3. Up-
date-specific architecture approval and automatic execution-capability
probing remain unimplemented.
Repeated --source ARTIFACT=SOURCE_ID explicitly associates selected new ar-
tifacts with active registered sources. It is a user-confirmed origin asso-
ciation for local delivery, not proof of retrieval or signature verifica-
tion. Payload source-name/origin metadata cannot grant this association.
Unmapped new artifacts retain local delivery without a source ID. Duplicate
artifact mappings are invalid; a mapping for an unselected artifact re-
quires a decision. Missing or inactive registered sources return 6.
The plan displays each association and binds the registry digest and saved
alias record. Apply requires the same mappings and registry; changing an
alias after review invalidates the plan. The version-2 set journal saves
associations for recovery. Registry mutation is blocked while that journal
remains incomplete. Recovery verifies the source record of each completed
instance before continuing. An already installed provider keeps its origi-
nal source and alias; specifying a new association for a reused instance
returns 3. Source migration requires a separate operation, which is not im-
plemented yet.
An already installed dependency is reused only after its metadata matches
the cached archive, its payload and ownership pass checks, and its own se-
lected dependency edges remain unchanged. Its state, reason and graph re-
main intact; the new consumer records its edge to that provider. The plan
hashes the reused instance state as well as the selected manifests. Apply
and recovery verify it again and never reinstall it. The preview labels it
installed. Re-requesting an already installed explicit root returns deci-
sion-required; this interface does not yet promote dependency reasons or
perform reinstalls.
Candidate discovery reads installed names and manifests for the package and
literal ELF-path requirements of the supplied candidates, then follows
their dependencies. For a HOLY/deps SONAME requirement it filters installed
packages by recorded arch/libc, then scans their cached archives and com-
pares actual DT_SONAME, ELF type and ABI scope; the name of a package or an
unverified claim is insufficient. Other discovery uses installed names and
manifests. Installed candidates currently require their cached archives for
ELF scanning. A missing archive returns unavailable instead of guessing
ABI facts. Reuse requires the version-2 or version-3 installed graph for-
mat. Multiple viable providers still require --choose; automatic in-
stalled/source preference ranking remains open.
Dynamic sets require literal absolute PT_INTERP paths. Absolute DT_NEEDED
paths must name exact provider payload paths. For a bare SONAME, the con-
sumer must have a nonempty list of absolute RUNPATH or RPATH directories,
or directories based on $ORIGIN. Holy resolves $ORIGIN from the consumer
path inside target root and rejects traversal above that root. The selected
provider must own a matching ELF file at the first existing DIRECTORY/SON-
AME, or own its symlink chain. The planner checks architecture, runtime,
symbols and version needs, and rejects unknown tokens or empty path en-
tries. It rejects a later provider when an earlier directory contains a
file at the same SONAME. Installed check repeats this path-order check
against the target root. Cached replacements use the same validation.
Loader defaults, other tokens paths and aliases crossing package ownership
remain unresolved. Parent directories must exist or be declared in that
package manifest; installation does not traverse symlinked parents. This
interface does not automatically rewrite ELF files or resolve loader
aliases.
An interrupted set leaves generation unchanged unless final commit already
published it. db status returns 5 for a recognized set journal. db recover
--finish-set verifies cached artifacts, the original plan hash, installed
metadata/graphs and actual payloads before completing generation publica-
tion and removing the journal. --continue-set additionally installs pack-
ages that have no installed instance yet, after checking the whole remain-
ing set. Already written payload must match exactly; missing entries are
restored. Neither mode overwrites a partial file or repeats a partially
written instance. Those cases require inspection. Recovery rejects mixed
journals, changed artifacts, ownership conflicts and drift in completed
package files. The cache must retain the selected artifacts.
db rm lists installed consumers and requirements that still reference the
provider. Without --accept-broken it returns decision-required status 3 and
leaves the root unchanged. The explicit flag permits removal while retain-
ing those consumers and their saved edges. db check then reports broken-
provider. Installing the original provider from cache through a new plan-
set restores the graph when the same requirement can be satisfied. A saved
graph belonging to a removed consumer does not keep its former dependencies
alive. A completed removal retains a transaction directory keyed by the
SHA-256 of its plan, with the removed artifact, original generation, ac-
cept-broken decision and a committed marker. The removed instance record is
retained there as old-instance before the database generation changes. Re-
covery uses that record if removal stopped after the instance left the in-
stalled directory, and retains the decision when it completes. Grouped re-
moval remains pending.
check without a reference inspects all installed instances. check
SOURCE:PACKAGE finds the installed slot using its recorded source ID, in-
cluding a source that is now inactive. files prints sorted absolute paths
from that slot's installed manifest even when a live file has changed. It
does not require the cached archive. When arch/libc variants make the ref-
erence ambiguous, these commands return decision-required status 3 until
--arch or --libc selects one. rm SOURCE:PACKAGE resolves the same slot and
shows its artifact hash before confirmation. Without a terminal it returns
decision-required status 3; --yes confirms removal of that selected arti-
fact. --accept-broken separately permits removal despite dependent in-
stalled packages. The database engine rechecks files, dependency edges and
journal state under its writer lock.
why SOURCE:PACKAGE reads the installed graph and prints one shortest saved
dependency path from an explicit root to that package. A package with no
path from an explicit root is marked orphan. The command reads no cached
archives and makes no changes. It refuses incomplete or malformed installed
graphs and missing provider instances. JSON output uses schema holy-why-1
with path, orphan and summary events. The path reports package identities;
exact requirement names remain in the saved graph and are not included in
this view.
add local:FILE stages a verified native artifact in the selected target-
root cache, builds a read-only package-set plan and shows its selected
packages, providers and plan hash. --candidate adds local archives to the
resolver pool; only selected packages are installed. --choose binds one
stable requirement ID to an exact provider artifact hash. The default root
is /. Local archive metadata cannot assign a trusted source-id. --asso-
ciate-source ALIAS is an explicit confirmation that the root local artifact
belongs to an active registered source. The manager resolves the alias to
its immutable source-id under the database lock and binds that ID into the
reviewed plan. This flag does not infer provenance from archive metadata,
verify a publisher signature or associate the candidate dependency
archives. The association requires a registered active source and is
checked again before apply. --associate SHA256=ALIAS binds a staged root
or candidate artifact to an active registered source. The hash must match
an artifact supplied to this add operation. Repeat the option for packages
from different sources; duplicate hashes are rejected. This is an explicit
provenance decision for each artifact, not signature verification. The re-
viewed plan records each binding and checks it on apply. --accept-arch
SHA256 confirms placement of one selected artifact whose architecture dif-
fers from the native target. It does not change the target metadata or
prove execution. --accept-privileged SHA256 confirms setuid placement for
one selected artifact. Both options may be repeated for distinct artifact
hashes. The plan records each decision and apply checks it again; an ap-
proval for a previous version is not inherited.
With a terminal, add asks for y before applying the displayed plan. An n
answer leaves installed state unchanged. Without a terminal, add returns 3
after printing the plan and stages no installed files. --noninteractive
forces this behavior unless --yes is also given. --yes approves only the
exact plan displayed by this invocation; the engine rebuilds it under the
writer lock and refuses a changed generation, artifact or provider choice.
--yes does not resolve missing dependencies. Architecture and setuid deci-
sions require their exact-hash flags. The cache may retain staged objects
after a declined or unresolved operation. Automatic doas escalation, source
lookup and script execution are not part of this local subset.
add SOURCE:PACKAGE reads one sealed native mirror made by sync. --catalog
selects a mirror for this invocation; without it the manager reads the mir-
ror bound to the active source-id by source catalog bind. The active
source-id and URL must match the mirror record. For an index with complete
dependency, file and ELF SONAME records, the manager opens the root archive
and indexed candidates reachable through declared requirements, interpreter
paths, shared-library names and script interpreters. It verifies each se-
lected candidate against the index before staging. Older indexes still use
the full candidate pool. If multiple packages share the root name, it re-
turns decision-required (3). Every newly selected catalog package receives
the registered source-id; reused installed providers retain their existing
source. The reviewed plan binds the catalog index digest and selected arti-
fact hashes. Before apply the manager rechecks the source and catalog;
changes require a new decision. The transaction journal retains source
bindings and index digest for recovery without network access. An unsigned
mirror record proves only local provenance. A source with a registered pub-
lic key requires a signed mirror and rechecks its signature. An unrelated
corrupt archive in a current indexed mirror does not block add; source cat-
alog bind and sync still verify entire mirrors. The current add path can
combine explicitly named candidate packages from other active sources with
--candidate SOURCE:PACKAGE. Each candidate source must have a bound sealed
mirror. The manager stages its indexed dependency closure, records the
source-id of each selected artifact, and rejects an artifact hash offered
by two candidate sources. It checks each candidate mirror's source-id, in-
dex digest and staged hashes again before apply. The root catalog index is
saved in the transaction journal; the exact selected artifact hashes and
source bindings are saved for every source. --candidate-provider
SOURCE:KIND:NAME selects matching packages by exact indexed package, file,
command or SONAME facts from the named source, then stages their in-source
dependency closure. The selected package still undergoes payload and re-
solver checks. The option requires a native index with complete dependency,
file and SONAME records; an unavailable index returns status 6 and a com-
plete index without a match returns status 4. Multiple matching packages
remain candidates for the reviewed provider choice. --candidate-local
SOURCE=FILE.holy adds a verified local artifact to the same resolver and
set plan as the native root package. SOURCE must be an active registered
alias; the option records the user's explicit source association and does
not infer trust from the artifact metadata. This accepts outputs from for-
eign import, including APK. The manager stages the artifact by hash, binds
its source-id in the plan, and rechecks the source and local file before
apply. Dependencies and ownership are checked by the usual set engine. Fur-
ther foreign dependencies need additional candidate inputs until a source
adapter can discover them. When a set has an unsatisfied exact package,
package-or, file, command or SONAME edge, add probes active bound native
catalogs for that edge before staging foreign artifacts. One matching
source with complete coverage supplies its providers and in-source closure.
For multiple offers, add prefers the missing consumer's registered source-
id, then its saved parent source-id, then a matching source family. It next
compares configured source priorities within the same group; larger values
win. It prints each rank and priority. An --answers entry overrides these
preferences. Remaining ties, or a family/priority match alongside unavail-
able coverage, list aliases and ask for one on a TTY. Active foreign
sources are skipped during this native-catalog probe; their absence of a
native mirror is not counted as a failed native lookup. For a scanned ELF
SONAME edge, the source probe checks the consumer's machine, runtime,
strong version needs and provider-attributed imported symbols against each
indexed candidate. A same-name library missing such a symbol is not of-
fered. An explicit SONAME metadata requirement without a consumer ELF path
uses exact indexed-name lookup. Without a TTY or under --noninteractive,
add returns decision-required; --candidate-provider names the choice in a
subsequent command. --yes approves the final plan but does not choose an
ambiguous source. The loop stops when the set is complete or no new arti-
fact is found. It checks installed providers first; an inactive origin can
still satisfy a new consumer. An edge without a catalog match does not hide
another missing edge in the same consumer; a required SONAME can still be
found after an interpreter or libc path already satisfied by an installed
package. An unavailable catalog leaves coverage incomplete and returns 6
when no provider can be found. Older indexes without file or SONAME facts
report unavailable coverage for those requirements. Selected packages re-
tain their source IDs, and candidate catalogs are checked again before ap-
ply. Fuzzy suggestions and runtime plugin discovery remain open. Executable
hooks remain outside the supported installation subset. Recorded transforms
are accepted after payload verification; no transformation runs during in-
stallation.
For add SOURCE:PACKAGE, --answers FILE supplies source decisions by stable
consumer artifact hash and requirement-id. The file uses the holy.conf
lexer: format holy-answers-1 on its first non-comment line, followed by
records source CONSUMER_SHA256 REQUIREMENT_ID SOURCE_ALIAS. Quoted tokens
are accepted. The manager copies the file before staging candidates, re-
jects duplicate consumer/id records, and checks that the named source actu-
ally offers the requirement. Other decisions still use their documented
flags. An answer tied to an old consumer hash cannot silently select a
provider after that artifact changes.
up SOURCE:PACKAGE finds one installed slot by its immutable source-id, name
and optional arch/libc. It checks the old artifact in the target cache,
verifies the pinned catalog index, and compares its records in the same
name/os/arch/libc slot. It stages only the chosen artifact and checks it
against its index record and actual payload. Other versions are not opened;
a broken unselected archive does not block this slot update. Known pacman,
deb, apk, xbps and holy comparator families select the highest newer ver-
sion; unknown or mixed comparator families require --choose SHA256. The
chosen digest can explicitly select a rebuild or older version. No newer
candidate leaves the system unchanged and writes no plan. APK compares its
full upstream pkgver, including -r revision. XBPS combines version and re-
lease with the source _revision convention before comparison. An un-
parsable version or an older index without comparator metadata still needs
an explicit choice. An absent old artifact, missing source slot or changed
catalog is reported. Without --prepare, up prints the full proposed plan,
saves it, then asks for approval before applying the exact file and hash
through the same apply command. --yes approves only that plan; it does not
choose an ambiguous version or approve architecture and privileged-file de-
cisions. Without a terminal or with --noninteractive, missing approval re-
turns 3 and leaves the plan file for review. --output selects its path;
otherwise up creates a private temporary plan path. Successful direct ap-
plication removes that temporary file. --prepare only writes the plan and
retains its temporary path; --yes is invalid with it.
Preparation writes a new mode-0600 plan file with the source-id, catalog
path and index digest, old/new artifact hashes, exact version decisions and
the complete update plan. It changes only the target cache. Review the
file, then run apply PLAN --sha256 PLAN_SHA256 with its whole-file SHA-256.
Apply snapshots and verifies the file, checks its embedded update-plan di-
gest, active source and unchanged catalog generation. It checks that the
new artifact belongs to the installed slot in that exact index, even when a
cached artifact would satisfy the embedded database plan. It then rebuilds
the plan under the writer lock. A changed database, source or payload needs
a new plan. Successful apply uses the existing journaled cached-update en-
gine; its recovery command is db recover --update. This subset prepares one
slot at a time. Grouped updates, signed catalogs and root/VM trials remain
open.
The db plan command reads the prepared reservation at the current database
generation. It verifies a private snapshot of the cached artifact, checks
root-path collisions and refuses unresolved requirements, interpreters and
ELF dependencies. The supported planning subset is linux/nolibc with noarch
data or the native host architecture (x86_64 on x86_64, x86 on i686).
Hooks must be empty. HOLY/transform may record completed payload changes.
Payloads may contain ordinary directories and regular files and relative
symlinks owned by the current UID/GID. Hardlinks, privileged modes and
writes inside holypkg state or cache trees are excluded. Output includes
target-root device/inode, generation, artifact SHA-256, path count and a
SHA-256 over those identities and ordered manifest entries, including sym-
link types and targets, and the canonical resolver record. That record
identifies artifact candidates, not physical loader bindings. Apply stores
the approved record as installed graph with a SHA-256 in instance state.
Symlinks must have mode 0777. The narrow local planner rejects a second ac-
tive version of the same slot, even when its payload paths differ. A slot
consists of source-id, name, os, arch and libc; unassociated local delivery
uses source-id "-". Source aliases and package versions do not change the
slot. Distinct slots may coexist when their file ownership is compatible.
The installed directory remains keyed by artifact hash, so registering the
same artifact as multiple installed instances is still unsupported. Slot
replacement requires update transactions, which are not yet implemented.
This command does not persist a plan, reserve ownership, authorize apply or
recheck the target root at mutation time. Concurrent changes can invalidate
the collision check. Apply revalidates inputs and paths under its writer
lock.
The db approve command recomputes the plan under an exclusive database
lock. If its SHA-256 matches PLAN_SHA256, the command replaces the pre-
pared reservation with an approved record bound to its artifact and genera-
tion. Status reports the approved digest; cancel removes the record. A
mismatched plan returns decision-required without changing the reservation.
Approval does not install files or authorize a future implementation to
skip path, artifact and generation checks under the same writer lock at ap-
ply time.
Database recheck compares an approved record with a fresh verified cache
snapshot and target-root collision preview under a shared database lock.
It returns 5 without an approved record, 3 if the plan digest differs and 4
for a path conflict. The check changes neither database nor rootfs. Other
processes can change rootfs paths after recheck; it cannot replace the
writer-locked validation required by apply.
Plan construction checks each payload path against installed manifests as
well as rootfs. An absent file remains owned until its installed claim is
removed; another package cannot acquire that path by recreating it. Shared
directory claims remain permitted. These checks read manifests directly;
generated ownership indexes are not required for correctness.
Database apply installs only an approved linux/nolibc package with empty
hooks and requirements. HOLY/transform is retained as a record of completed
changes and is never executed by apply. Its payload may contain ordinary
regular files, including native static ELF executables, relative symlinks,
direct hardlinks and directory entries. Existing directories must match the
manifest's mode and numeric owner/group. Missing parents require explicit
directory entries in that package; apply creates them in parent-first order
before writing files. A changed directory is rejected before writing a
journal. No absolute symlink, privileged mode or file collision is ac-
cepted. The caller must review the db plan digest and invoke db approve
first. Apply holds the database writer lock, recomputes the approved plan
and rechecks every path against opened directory descriptors. It creates
each regular file with O_EXCL, then synchronizes it and its parent. It
creates symlinks with symlinkat without replacing existing entries and syn-
chronizes their parent directory. Hardlinks use linkat after their verified
regular targets are available, with no replacement of existing names. All
parent paths must be real directories; payload writes do not follow in-
stalled links. Relative targets may be dangling. Lexical containment
checks do not prove the runtime meaning of symlink chains. It records a
journal before changing the rootfs and publishes installed metadata and the
next database generation after copying payload files. The installed record
contains meta, files, deps, origin and state; local delivery has no trusted
source-id. Apply does not run package hooks or configure services. De-
clared directories require local numeric ownership, owner search permis-
sion, and no group/other write or privileged mode bits. Apply prepares an
empty publishes it with renameat2(RENAME_NOREPLACE) and synchronizes the
parent. Missing renameat2 support prevents publication. Existing directo-
ries are never chmodded or replaced. Archive entry order does not determine
creation order. A complete staged directory can be reused by set/up-
date/repair recovery; a partly initialized or nonempty staging directory
requires inspection. Removal retains directories and unregistered con-
tents.
An I/O failure after journal publication leaves an incomplete transaction
for inspection; db status returns 5 and db recover does not retry file mu-
tations. db recover --abort-empty can clear an incomplete journal only at
its original generation, with the same approved artifact and no installed
instance or payload files. Already created directories remain in place. It
retains approval for a later apply or cancel. Otherwise the operator must
inspect the rootfs and journal manually. A crash after publishing the next
generation can leave the journal and approved reservation behind. db re-
cover --finish-apply verifies the installed instance, its generation, all
listed payload files and any remaining approval before clearing those
records. It refuses an incomplete payload and does not resume file extrac-
tion. A regular successful return requires both the installed record and
generation publication. General ownership replacement and automatic roll-
back are not implemented. The command is not an installer for arbitrary
.holy packages.
db plan-update OLD_SHA256 NEW_SHA256 --root DIRECTORY previews one explicit
cached replacement. It keeps the installed source-id, name, OS, architec-
ture and libc slot; a different slot or an already active target artifact
conflicts. For a non-native target, plan-update requires --accept-arch
NEW_SHA256 even when the old installed package had approval. The plan
records host and target, and the new installed instance retains target
metadata with an unverified execution status. Apply repeats the flag; re-
covery reads it from the journal. This authorizes placement, not execution
by the current kernel. If the new artifact contains a setuid executable,
plan-update requires --accept-privileged NEW_SHA256. The old installed de-
cision only authorizes checking and replacing the old file; it does not ap-
prove the new hash. Apply must repeat the same flag. Update journal ver-
sions 2, 3 and 4 bind privilege, architecture and both decisions, respec-
tively, to the new artifact. Setgid, sticky and privileged hardlink groups
remain unsupported. It requires the old archive, the proposed archive and
archives for every other installed package in the cache. It does not select
versions or download missing dependencies. The caller explicitly proposes
local delivery in the installed source's slot; metadata inside the new
archive cannot assign a source-id or provide signature evidence.
The command takes the database's shared lock, refuses pending transactions
and checks installed metadata, file ownership, payload and saved provider
edges. It verifies the entire proposed set with the old artifact replaced
by the new one. A broken dependent outside the updated package's own depen-
dency graph therefore prevents a valid plan. New dependencies must already
be satisfiable by that proposed set; ambiguous edges return decision-re-
quired. Installed reasons and all current state hashes remain bound to the
plan.
The first output line reports the SHA-256 of the following UTF-8 record.
The holy-update-plan-1 record has update, installed, files and graph sec-
tions. It includes database generation, root/database device and inode,
old/new hashes, source registry digest and current source alias, every in-
stalled state hash, the canonical file delta and the proposed graph. Re-
peating a preview with the same inputs produces the same bytes. Alias
changes keep source identity but invalidate the plan; a deactivated regis-
tered source returns unavailable (6). Unassociated local packages retain
source-id "-".
This preview writes no installed state or payload and executes no package
code. Missing or modified ordinary installed files conflict. A modified
regular file marked config in both artifacts stays at its public path; the
incoming payload is planned at PATH.holy-new. The plan binds the observed
content hash and mode. An existing PATH.holy-new must match the old arti-
fact exactly; the update then replaces it. A changed or colliding
PATH.holy-new conflicts. Directory metadata changes, hooks, new provider
selection and multi-package replacement are not implemented. The printed
record is not accepted by db apply or db apply-set. JSON output is not im-
plemented.
The internal file planner records retained, added, replaced and removed
paths with stable IDs and archive hashes. Unsupported entries remain in the
record and prevent application. Recovery checking accepts exact old or new
objects and rejects unrelated changes.
db apply-update PLAN_SHA256 OLD_SHA256 NEW_SHA256 --root DIRECTORY applies
that cached replacement after reconstructing and comparing the approved
plan under the writer lock. It preserves explicit/dependency reasons and
source identity, rewrites each installed consumer's own provider edges, and
increments the database generation once. A changed root, registry, genera-
tion, artifact or installed state requires a new preview.
Before changing target files, apply checks reserved sibling names, pub-
lishes an immutable journal and plan under transactions/update, prepares
the next installed database, and stages all changed payload. The progress
record tracks the latest file transition ID and its intent/result. Files
become visible individually; the operation does not atomically switch the
whole rootfs. Database publication requires renameat2(RENAME_EXCHANGE),
tested on that filesystem before payload mutation. After validating the
new payload, database and generation, apply retains the old database and
journal under transactions/PLAN_SHA256. It retains cached old archives.
These records can supply a reviewed reverse update through holypkg roll-
back.
holypkg rollback accepts the hash of a committed update transaction. It
checks the committed marker, journal and exact plan hash, then previews a
reverse replacement using the cached old artifact and the current depen-
dency graph. The rollback-plan line gives the SHA-256 of the following up-
date plan. --apply requires that exact hash; a stale plan or changed root
fails before mutation. The reverse replacement gets its own update journal
and recovery path. --accept-arch and --accept-privileged are artifact-
scoped decisions for the restored artifact. Rollback is limited to one com-
mitted update whose new artifact still occupies the installed slot. Missing
cache objects return 6. This operation restores package files and depen-
dencies. It does not reverse external hook effects, migrations or user
data.
When an update preserves a modified config, the installed instance stores
the source HOLY/files as package-files and records the local files manifest
plus its hashes in config-state. The local manifest owns both the preserved
public config and PATH.holy-new. check and owner use that manifest; a later
package update carries it forward. Changing the preserved file after the
plan was reviewed invalidates apply. Package removal leaves config files on
disk and removes PATH.holy-new. Missing-only repair maps archived config
bytes to PATH.holy-new and checks the installed manifest after extraction.
It cannot recreate a missing preserved public config from the archive and
rejects that case before journaling. A repair plan binds the installed man-
ifest hash.
Hardlink updates stage one inode per new group and link each changed mem-
ber's temporary path to it. A group-stage record binds the reserved anchor
sibling name to the plan. Retained anchors keep their inode. Group staging
links remain until the new database and generation are published; cleanup
validates the final groups before removing them. Equal bytes alone do not
satisfy a group split or merge. External hardlinks to replaced files keep
the old inode.
db recover --update revalidates the immutable plan, cached inputs and
old/new file states before continuing the interrupted operation. It recog-
nizes a completed database exchange or generation publication and does not
repeat them. Other state operations refuse the active update with status 5.
Recovery preserves unexpected or partly written reserved staging objects
and returns 5 for inspection; it does not delete them automatically. Miss-
ing cached inputs must be restored. Interruption before publishing the im-
mutable journal requires manual inspection. Unchanged files must still
match their recorded state. Recovery can finish a reviewed PATH.holy-new
publication; it does not execute hooks or merge file contents. An inter-
rupted setuid update can leave a reviewed executable under a reserved mode
are shown in the file plan; root operators must treat it as installed code.
The internal resolver can also validate a complete proposed artifact set.
It requires every supplied artifact, including disconnected consumers and
dependency cycles. Missing requirements fail the set; ambiguous provider
edges still require a decision. The caller must construct the installed set
after replacement and preserve instance reasons and source bindings. This
interface does not discover update candidates or apply transactions.
Static executable installation verifies ELF architecture and nolibc facts;
it does not execute package programs. The caller may run an installed pro-
gram as a separate action. Executable payloads with an unknown format are
refused; an executable script with a direct absolute shebang creates a
file-specific dependency edge to an executable ELF at that exact path in
the selected set. For relative symlinks in the supplied candidate set, the
planner follows each alias and records a separate provider edge for its
owning package and the final executable ELF. It rejects ambiguous aliases
and chains longer than 16 hops with a decision-required result. These edges
are saved in installed state and block provider removal unless the caller
accepts broken dependents. The planner requires a decision for env, mal-
formed shebangs and paths it cannot resolve. For a newly added script, the
planner reads existing relative aliases inside the target root and aliases
in the supplied .holy candidates, then adds reachable installed owners and
the final ELF owner to the candidate set. This discovery needs openat2
with root-confined path resolution; when the syscall is unavailable it re-
turns a requirement error. Foreign architecture, including x86 on x86_64,
returns requirement error 6 until architecture override decisions are im-
plemented. No metadata is relabeled.
Database check reads one installed manifest and compares its regular files
against the target root by path, type, size, mode, numeric ownership and
SHA-256. It compares symlink type, mode, numeric ownership and exact read-
link target without following the link. It checks listed directories
against their manifest mode and numeric owner/group and rejects symlinked
parent paths. It returns 4 for changed or missing files, 6 when the in-
stance is absent, and 5 while a file transaction is incomplete. The command
neither downloads an archive nor repairs a file. This restricted check also
checks recorded interpreter, shebang and needed-path providers against
their installed manifests. Missing or changed provider files produce bro-
ken-provider findings with the absolute target path. For an intact exe-
cutable script it checks the shebang interpreter within the target root. A
missing interpreter fails; a malformed shebang, env command, invalid inter-
preter ELF or unresolved path reports unknown. This does not test inter-
preter libraries or runtime behavior. A missing openat2 syscall returns 6
and reports unknown coverage. For saved SONAME edges, check opens intact
provider ELF files through the target root and compares actual SONAME, ELF
class, machine and strong version needs. A direct literal loader directory
can pass; unresolved loader context reports unknown-loader-context. This
does not model general loader search order. --all walks every supported in-
stalled manifest under the same shared lock and reports each artifact as
intact, changed or unknown in digest order. It returns 4 if any artifact
has changed or remains unknown. An empty installed set reports zero checked
packages. --json emits one holy-installed-check-1 JSON line per artifact
followed by a summary of passes, failures and unknowns, or one error line
on an invalid database, missing instance or incomplete transaction. Each
artifact includes a findings array with code, severity and relative path
for each mismatch. Script interpreter findings also include target. miss-
ing-file identifies an absent file, link or directory, including a path be-
low a missing parent; changed-config identifies a modified regular file
marked config. changed-file covers other manifest mismatches, including in-
accessible or symlinked parents. Paths encode non-ASCII bytes as \u00HH;
consumers reconstruct the original bytes. Findings follow manifest order;
intact artifacts have an empty array. The artifact-level code names the
first finding. Coverage is data-manifest-and-direct-shebang, including se-
lected literal-path provider files and the supported direct SONAME case.
The coverage label is retained for the current JSON schema.
Hardlink groups are checked by device and inode as well as contents and
metadata. Independent copies with matching bytes fail the group check.
Missing-only repair reuses an intact surviving group member when its regu-
lar anchor was removed; when the whole group is absent it restores contents
from the cache and links all declared names. Removal unlinks only owned
paths and preserves external hardlinks. Cross-filesystem link failures
leave a journal and report the linkat error. Cached updates also verify
group topology during partial publication and recovery. Full manifest
checks reject undeclared inode sharing between independent files or differ-
ent groups within that manifest.
db repair-plan SHA256 --root DIRECTORY prepares missing-only restoration of
an installed artifact with a recorded dependency graph from the target
cache. db repair SHA256 --plan PLAN_SHA256 --root DIRECTORY applies the re-
viewed policy. The plan binds the artifact, stored graph, database genera-
tion and root device/inode. Its scope is all absent files in that artifact,
including files removed after preparation. Changed or partial files are
preserved and require manual review. Declared missing directories are re-
stored; existing directory metadata must remain intact. Repair does not run
hooks or change providers. holypkg repair SOURCE:PACKAGE resolves an in-
stalled source slot and prints the same read-only plan. Passing --plan
PLAN_SHA256 applies that exact plan. An inactive source alias remains re-
solvable through its recorded source ID. When multiple arch/libc slots
match, --arch and --libc select the intended slot.
Repair records stage repairing before writing files, verifies restored con-
tents and increments the generation once. An interrupted operation remains
pending. db recover --repair resumes that recorded policy after verifying
cache, installed metadata, ownership and graph. Partial files are not over-
written. Ordinary check remains read-only. This is not general upgrade,
rollback or configuration-file repair.
make bootstrap-musl ARCH=i686|x86_64 INPUTS=DIRECTORY OUTPUT=DIRECTORY
builds pinned musl 1.2.5 as a native runtime package, preserving source and
build hashes and licenses. The default target is x86_64. The i686 build re-
quires multilib GCC and uses the x86 package architecture and i686-linux-
musl private runtime path. Both packages can coexist; their documentation
and public loader paths differ.
make check-musl-abi STATIC_HOLYPKG=FILE MUSL32_PACKAGE=FILE MUSL32_CC=FILE
MUSL_PACKAGE=FILE MUSL_CC=FILE compiles and runs ELF32 and ELF64 pthread
probes in one disposable root on an x86_64 kernel. The fixture explicitly
approves the 32-bit artifacts, tests pipes in both directions and restores
either or both removed runtimes through the static client and local cache.
It requires doas, mount namespaces, patchelf and working 32-bit execution.
This test does not boot an i686 kernel or test glibc32 or hardware. The
build sets libc.musl-x86_64.so.1 as SONAME at link time; it does not
rewrite the self-bootstrapping loader with patchelf. make check-libc-re-
covery STATIC_HOLYPKG=FILE MUSL_CC=FILE MUSL_PACKAGE=FILE requires a static
client, a musl compiler and that runtime package. It installs glibc and
musl fixtures, runs both, removes each runtime and both together, then exe-
cutes cached repair inside the libc-free chroot. doas is required; guest
commands run with the caller's UID/GID. This tests local recovery on
x86_64, not boot, i686, network recovery or general foreign package conver-
sion.
make bootstrap-glibc ARCH=i686|x86_64 INPUTS=DIRECTORY OUTPUT=DIRECTORY
builds the pinned glibc-2.42.tar.xz source as an ordinary user in a sepa-
rate build directory. The default is x86_64; i686 requires an x86_64
builder with multilib GCC/G++. The i686 package uses arch x86 and
/usr/lib/holy/i686-linux-gnu, with a relative public ld-linux.so.2 link and
separate license/documentation paths. Host GCC, G++, binutils, kernel
headers, make, gawk, bison, Python and patchelf are required. --without-gd
avoids linking the optional memusagestat tool against the host's libgd clo-
sure. The output glibc.holy contains libc.so.6, its loader and upstream li-
censes. It records input hashes, build configuration and explicit private-
path patches in bootstrap origin records. It is not yet a complete glibc
SDK or locale/NSS package set. build.record distinguishes building from the
separate runtime probes; the full upstream test suite is not implied by a
successful build. make check-bootstrap-glibc OUTPUT=DIRECTORY runs that re-
tained build's upstream test suite as an ordinary user and returns its ac-
tual status. GLIBC_PACKAGE can supply this artifact to check-libc-recovery;
without it that fixture snapshots the host glibc pair.
make check-libc-abi takes the same inputs as check-musl-abi plus
GLIBC32_PACKAGE=FILE and GLIBC_PACKAGE=FILE. It installs four runtime pack-
ages and four architecture/libc slots of one probe package. It runs
pthread, clock and pipe probes for every ordered pair, removes each run-
time, each libc family and all four runtimes, then checks and repairs
through the static client inside the target root. JSON records runtime, ap-
plication, client and probe source hashes. Glibc probes use host multilib
headers and CRT objects; this does not validate a packaged SDK, upstream
glibc tests, NSS/locales or i686 boot.
Database owner reads installed manifests and lists the artifact digest and
kind for an exact relative or root-absolute path. Multiple packages may
list the same directory. If multiple records claim one regular file or sym-
link, the command returns conflict status 4 without listing an arbitrary
owner. An absent path returns 6. It holds a shared database lock and does
not require the path to exist in the live rootfs; db check compares actual
contents. The top-level owner command calls this database lookup for the
selected root.
Database rm removes one intact supported installed instance by artifact
SHA-256. An object package several installed applications depend on has one
owner and several dependents: removing one application leaves the object
and the other application untouched, while removing the object itself re-
turns decision-required until the caller passes --accept-broken, and db
check --all then reports the provider that is gone. It verifies all listed
files before mutation, refuses changed files and pending reservations, then
writes a removing journal. Before journaling, it also checks the other in-
stalled manifests for conflicting ownership claims; shared directories are
allowed. A conflict returns 4 without deleting files. The removing journal
records whether the operation accepted broken dependents; recovery accepts
only the documented decision marker. Recovery repeats this ownership check
before resuming an interrupted removal; conflicts retain the journal and
return 5. It unlinks only listed regular files, symlinks and the instance
record; shared directories and the cached artifact remain. The database
generation advances after removal. If an I/O failure interrupts deletion,
db status reports an incomplete journal. db recover --continue accepts a
removing journal at its original generation with an intact installed
record, treats absent listed files as removed, verifies every remaining
file without mutation, then completes removal. Changed files or partial in-
stalled records require manual inspection. --abort-empty applies only to an
installing journal, never to removal. This command does not remove depen-
dents.
The local info command reads an LZ4-frame-compressed tar archive and prints
the seven required fields from HOLY/meta. It reads archive data without ex-
tracting or executing it. It checks for repeated metadata and unsupported
format versions. It prints the SHA-256 digest of the input archive. It
rejects archive entries outside HOLY and DATA, including dot segments. It
requires all seven HOLY metadata members as regular files and rejects du-
plicate members. It does not validate the payload manifest, hashes, signa-
tures or installability.
search QUERY without --source visits every active source in alias order. It
prints each source alias and immutable ID with its package or file results.
Native sources use their sealed mirrors. APK sources search their bound
repositories and label results with the repository name; --repo restricts
an explicit APK source to one repository. XBPS sources query the bound re-
podata for --arch; an explicit XBPS query without it returns decision-re-
quired (3). APT sources query the bound Packages index for --suite, --com-
ponent and --index-arch; an explicit APT query without them returns deci-
sion-required (3). APT --file uses its partial Contents index when
present. XBPS file coverage is unavailable. Missing or unsupported source
catalogs report unavailable coverage. An empty active registry exits 6.
--catalog requires --source and applies to native mirrors or a bound
APT/XBPS catalog. search QUERY --source SOURCE checks the active source-id
and the bound catalog before printing matches. info SOURCE:PACKAGE checks
the same inputs; APK info can require --repo when multiple repositories
match. Neither command installs or runs package code. APK package search
uses the checked package index; its file coverage is unavailable, so --file
on APK returns 6. Native exact search and info leave unrelated archives un-
opened; source binding still audits the complete native catalog. An un-
signed mirror has no publisher authentication; a keyed source rechecks its
signature. With --file, QUERY must be an absolute path. A native version-3
index records every verified nondirectory payload path and declares com-
plete file coverage. The result prints matching packages, the coverage
state, index digest and index timestamp. A miss in a complete index returns
zero matches; a legacy index reports unavailable coverage and exits 6 be-
cause absence cannot be established. File queries verify the digest-pinned
index and only matching archives; an unselected archive can change without
invalidating the query. No package code runs. --fuzzy returns ranked sug-
gestions for package names. With --file --fuzzy, QUERY may be a basename or
absolute path; only file basenames from a complete index contribute sugges-
tions. A suggestion prints its score, matched name or path and package
record. Up to 20 suggestions are shown, in score and index order. Scores
prefer ASCII case-insensitive equality, prefix, substring, then one or two
byte edits. These hints never satisfy a dependency or replace an exact SON-
AME. File hints from a legacy index report unknown coverage and exit 6.
The verify command checks regular DATA files against the supported
HOLY/files records, including size, mode, numeric owner and SHA-256. It
checks symlinks, directories and direct hardlinks, rejecting unsupported
objects. It does not install or authenticate packages; the two read passes
do not provide a stable filesystem snapshot.
The manifest command stages a local archive, verifies the entire supported
payload and HOLY/files, then lists validated objects in path order. Each
line contains type, escaped relative path, mode, numeric UID/GID and size;
files include SHA-256, links include targets and grouped hardlinks include
their group. Fields are borrowed from the verified snapshot during inspec-
tion. This read-only output does not prove that ownership can be mapped
onto a target root or that a file can be installed without collision.
The requirements command stages and verifies a regular archive, then reads
HOLY/deps using the holy.conf lexer. Its current typed subset accepts lines
of eleven tokens:
require ID CONSUMER KIND NAME ARCH LIBC RELATION VERSION ORIGINAL EVIDENCE
KIND is package, package-or, file, command, soname, symbol-version or
build. For package-or, NAME contains Debian branches as NAME@RELATION@VER-
SION joined by |; the outer ARCH, LIBC and RELATION fields are any and VER-
SION is -. ARCH is any, x86, x86_64 or noarch; LIBC is any, glibc, musl or
nolibc. RELATION is any, eq, ge, le, gt or lt. VERSION is - only for any.
ORIGINAL and EVIDENCE retain the source expression and basis as text. Re-
quirement IDs must be unique. The command rejects unsupported records and
duplicate IDs rather than dropping them; it does not evaluate version con-
straints or choose a provider. With --json, stdout contains holy-require-
ments-1 requirement events and a summary, or one error event on invalid in-
put. Each requirement event has id, consumer, kind, name, arch, libc, rela-
tion, version, original and evidence fields. The JSON encoder maps non-
ASCII bytes to \u00HH escapes; consumers reconstruct original bytes rather
than treating them as normalized text.
The solve command verifies and scans each explicitly supplied local
archive, with the first archive as the requested root. Its supported subset
accepts Linux packages with validated payload ABI facts, HOLY/hooks gated
by an explicit skip decision at installation and an ordinary HOLY/transform
record, and HOLY/deps package requirements with explicit arch/libc scopes
or any, plus unversioned file requirements at literal absolute paths and
unversioned bare command names. Each consumer must equal its package name.
The package's own metadata name and package claims in HOLY/provides supply
package capabilities. An alias retains its declared version; an unversioned
alias cannot satisfy a version constraint. Explicit claim scopes must
agree with the artifact's arch/libc; any inherits that artifact scope. Se-
lection preserves the actual package identity and records the requested
alias on the dependency edge. Version constraints eq/ge/gt/le/lt use the
comparator declared by both consumer and provider: pacman, deb or holy.
For pacman and deb, the full foreign epoch:version-release string belongs
in version; release remains the native artifact revision and is not ap-
pended a second time. A missing pkgrel on one side does not constrain
pkgrel. Numeric epochs, prerelease letters and separator widths follow pac-
man's rules. The Debian comparator uses epoch, upstream version and final
Debian revision, with tilde before the end of a part. Its validator re-
quires Debian version characters and a leading digit in the upstream part.
Both foreign comparators limit input to 65536 bytes; unsupported encodings
have no guessed order. The Holy native comparator uses decimal components
separated by dots and an optional lowercase prerelease suffix after hyphen
or tilde. It compares unbounded decimal runs by length and content, ignores
missing trailing zero components, and places prereleases before finals. For
updates, it compares release after an equal version. It limits input to
65536 bytes. A package must declare x-version-family holy; untagged native
versions keep manual update selection. The adapter filters candidates be-
fore the common libsolv solver receives exact requirement capabilities. The
mixed solver pool does not compare foreign EVRs. File requirements match
nondirectory paths in the verified payload manifest, including symlinks and
hardlinks. A file claim in HOLY/provides alone cannot satisfy them. The
chosen file owner and path enter the reviewed graph; removal checks in-
stalled file requirements. Versioned file requirements and nonliteral paths
remain unsupported. Command requirements match executable payloads named
in usr/bin, bin, usr/sbin or sbin. Symlink commands must resolve to an exe-
cutable in the same artifact; direct hardlinks retain the target's exe-
cutable mode. A command claim alone or a nonexecutable file does not sat-
isfy the edge. The chosen provider enters the installed graph, and removal
checks that edge. This test does not model a caller's custom PATH or shell
builtins. Versioned command requirements remain unsupported. Exact unver-
sioned SONAME requirements from HOLY/deps match DT_SONAME of an observed
ET_DYN library with the requested package arch/libc scope. A typed SONAME
claim without that ELF does not satisfy the requirement. This selects an
artifact candidate; physical loader visibility needs a later check. Unsup-
ported metadata dependency forms return decision-required when their con-
sumer is selected. Unsupported consumer version families return 6.
Providers from another or unknown family cannot satisfy a constrained edge;
if no matching candidate remains, resolution returns dependency-conflict.
A supplied --choose answer cannot bypass these checks. Unversioned require-
ments retain exact capability-name matching across families. Automatic
newest-version selection in solve and cross-family overrides remain pend-
ing. There are no source IDs, installed packages or architecture overrides
in this command.
ELF payloads add file-scoped interpreter, DT_NEEDED and strong undefined
symbol requirements, even when HOLY/deps omits them. A SONAME candidate
must match the exact name, ELF class, e_machine and inferred runtime. It
must be ET_DYN without DF_1_PIE, so an executable with a forged SONAME is
not a shared-library candidate. It must define the required version nodes
and export each strong versioned reference assigned to that SONAME, with
matching version and compatible symbol type. Weak undefined symbols do not
add mandatory symbol requirements. Weak definitions can provide strong ref-
erences. Hidden/internal symbols cannot provide external references; ex-
plicit versions may use compatibility exports, while unversioned references
require default exports.
The current unversioned-symbol scope covers the consumer, its direct
DT_NEEDED objects and interpreter. A choice for a SONAME must agree with
the package used for its unversioned symbols. Inherited executable/global
scopes and indirect lookup scopes are not modeled; an unresolved symbol re-
ports unknown-symbol-scope with code 3. An interpreter candidate currently
needs a directly recorded ELF at its literal path with an executable per-
mission bit. Unresolved interpreter paths report unknown-interpreter-con-
text with code 3, since symlink and merged-/usr package views are not mod-
eled by this archive-only solver. DT_NEEDED containing a slash requires a
launch context and returns 6. Nested requirements of selected library pack-
ages participate in the same libsolv graph.
ELF requirement IDs hash package name, arch/libc, consumer path, require-
ment kind and target. They remain stable when candidate order changes. Ca-
pabilities also include the consumer artifact digest so different versions
cannot silently share their provider constraints. ELF-edge events include
id, consumer artifact, path, kind, target and candidate/selected provider
artifact digests. On a decision or conflict, root ELF-edge events precede
the error to support explicit choices.
The command prints selected artifact SHA-256 digests only after a unique
package set is found. It returns 3 for multiple package sets, 4 for an un-
satisfied requirement, and 6 for unsupported or invalid input. No plan, in-
stall or mutation follows. Equal package names from two different artifacts
remain separate candidates; the caller must choose when both could satisfy
a requirement. Multiple selected providers for an edge also require a deci-
sion. Script interpreter and plugin requirements are not inferred from
payload in this subset, so selection is not an installability verdict. Li-
brary placement, duplicate SONAME files inside one artifact, loader search
order, RPATH/RUNPATH, ISA availability and symbol interposition require
later context checks; an artifact match does not prove that the runtime
loader will select its matching file. For a direct root requirement,
--choose pins a named requirement ID to the exact candidate artifact digest
supplied on the command line. The candidate must satisfy the requirement,
including a package capability, verified file path, executable command or
observed ELF edge; otherwise the command returns decision-required. The
choice applies to this solve only and does not assign a source ID or
rewrite ABI facts. Unsupported syntax returns 2. Transitive requirements
and multiple choices cannot yet be pinned by this option. With --json,
stdout contains holy-local-solve-1 selected events carrying artifact di-
gests and a count summary after a unique solution. Errors emit one struc-
tured error event without partial selected events; malformed arguments use
invalid-query. Diagnostics remain on stderr. For a missing provider on a
path of uniquely determined candidates, the error event also carries the
validated requirement-id and stderr names it. If a transitive edge has mul-
tiple candidate providers, this diagnosis stops at that edge rather than
blaming an unselected candidate.
Repository solve accepts a package name in a sealed local repository. It
validates the entire catalog and every artifact, freezes those artifacts in
temporary snapshots, then invokes the same restricted read-only solver with
the unique exact-name root first. Two versions of the requested name return
decision-required. A missing name returns unavailable-artifact. An invalid
generation returns invalid-catalog without partial results. --json uses the
same holy-local-solve-1 events as solve. This command reads all archive
payloads per query and supports the same package aliases and version con-
straints. It does not import remote sources or install packages. The
--choose form applies the same one-operation, direct-root requirement se-
lection after validating the sealed index and matching the selected digest
to an artifact in that generation. It does not write a saved rule. On suc-
cess, the text output ends with the sealed index SHA-256 generation; the
--json summary includes the same digest. A later index generation requires
a new query before treating the result as current. The digest binds the lo-
cal index bytes, not an authenticated publisher or a future installed plan.
The provides command stages and verifies a native archive, then reads
HOLY/provides as typed claims. Each supported line has seven tokens:
provide KIND NAME ARCH LIBC VERSION EVIDENCE
KIND accepts package, file, command, soname, symbol-version or build. ARCH
accepts any, x86, x86_64 or noarch; LIBC accepts any, glibc, musl or no-
libc. VERSION is text or - for an unversioned claim. File names must be
absolute; SONAME must have no slash. Duplicate KIND/NAME/ARCH/LIBC/VERSION
claims fail. The parser does not prove a claimed file, command or SONAME
exists, resolve a requirement or compare foreign version schemes. Reposi-
tory and cache writers reject unrecognized capability records rather than
publishing them as parsed. With --json, stdout contains holy-provides-1
capability events with kind, name, arch, libc, version and evidence, then a
count summary. A malformed archive or capability emits one structured error
event. Byte escapes in JSON map non-ASCII bytes to \u00HH; callers must re-
construct the original bytes.
The local fetch command copies any regular input file to an existing direc-
tory as SHA256.holy. It copies bytes without interpreting metadata, ex-
tracting files or running hooks. It creates a mode 0600 temporary file rel-
ative to the opened output directory and synchronizes it before a no-over-
write hardlink creates the object. Repeating the command checks the exist-
ing object's digest. A mismatched object is left untouched. Fetch does not
establish archive validity, provenance or a trusted root-owned cache. Con-
figured source fetching remains separate from the pinned HTTPS mirror com-
mand.
With --extract, DIRECTORY must not exist. The command copies the regular
input into a private temporary file, verifies that copy and checks that
every entry is a directory, regular file, relative symlink or direct
hardlink. Before creating DIRECTORY, it rejects duplicate paths and paths
nested under any non-directory archive entry. It then creates DIRECTORY
and writes HOLY and DATA there with directory-relative operations that
refuse symlink traversal and file replacement. It does not preserve
UID/GID, setuid bits, capabilities or xattrs. It refuses absolute symlink
targets and special nodes; hardlink targets must resolve to regular files
within the output tree. Hooks never run. If an I/O error occurs after DI-
RECTORY is created, partial output remains for inspection. This command is
not a system installation or a root transaction.
The local check command copies the regular input to a private temporary
file, verifies that copy and checks known ELF arch/libc mismatches, then
compares its DATA members with a supplied filesystem root. It follows di-
rectory components through directory descriptors without following sym-
links. It compares types, mode, numeric UID/GID, file size and SHA-256,
symlink targets and hardlink inode identity. It reports each changed path.
Check does not run package code, download files or modify the root. It does
not consult an installed database, model ELF loader search, check unknown
runtime dependencies or prove a stable snapshot if the root changes during
checking.
For matching regular ELF payloads, check reads PT_INTERP and tests whether
an executable ELF file with matching class and machine exists at that ab-
solute path inside the target root. Missing targets report missing-inter-
preter; the wrong class or machine reports incompatible-interpreter. The
lookup follows ordinary symlinks using openat2 with RESOLVE_IN_ROOT and RE-
SOLVE_NO_MAGICLINKS. Absolute symlink targets and parent traversal stay
within the opened target root; merged-/usr and final loader links are sup-
ported. A dangling link reports missing-interpreter. A loop, magic link,
invalid ELF or inaccessible path reports unknown-interpreter. A missing
openat2 syscall reports unavailable-path-resolution with requires=ope-
nat2:RESOLVE_IN_ROOT, unknown status and exit code 6. Presence and class
alone do not prove dynamic linking, compatible libc or nested library
availability. For a matching executable script, check reads the first she-
bang line and looks up its absolute interpreter inside the target root. A
missing target reports missing-interpreter. An existing executable ELF sat-
isfies this path check. A malformed, relative or overlong shebang reports
unknown-interpreter. For /usr/bin/env, check reports unknown-interpreter
after finding env because it does not resolve the command using the
script's runtime PATH. Script interpreters, their nested loaders and li-
braries are not recursively checked. If all findings are unknown, the sum-
mary status is unknown rather than fail; the check still returns nonzero
because it cannot report pass.
With --json, check prints one JSON object per finding and a final pass/fail
summary on stdout. The schema is holy-check-1; changed-payload and missing-
payload are stable finding codes. Input or operational failures use in-
valid-package or check-error and status unknown. Paths use byte escapes.
Only check, requirements, provides repo providers, solve, preview and data-
base preflight support --json in this prototype.
The developer-facing elf command reads a regular ELF through libelf/GElf
without executing it or invoking ldd. It prints ELF class, numeric e_ma-
chine and e_type, a recognized machine (x86, x86_64 or x32), the PT_INTERP
path, and a libc runtime hint only for recognized glibc or musl inter-
preters. It reads PT_DYNAMIC to list DT_NEEDED, SONAME, RPATH, RUNPATH and
DT_FLAGS_1 even without section headers. GNU DT_VERNEED records list
provider SONAME, version and weak flag. GNU DT_VERDEF records list version-
definition names. DT_SYMTAB and DT_VERSYM records expose each dynamic sym-
bol's binding, type, visibility, section index, version index, hidden-ver-
sion flag, version name and version-provider SONAME when recorded. Unde-
fined weak symbols retain binding=2 and section=0. IFUNC symbols retain
type=10; the reader does not execute their resolvers. Symbol counts come
from SysV or GNU hash tables. For an empty GNU hash with no SysV hash, the
reader needs the matching SHT_DYNSYM section; removing that section makes
the symbol extent unsupported. Other supported hash tables work without
section headers. Malformed symbol names, version indices, entry sizes or
address ranges reject the input. GNU x86 ISA-needed properties in PT_NOTE
report x86-64-baseline/v2/v3/v4 when present; absent or unrecognized prop-
erties report unknown. ET_EXEC without PT_INTERP, PT_DYNAMIC or DT_NEEDED
reports nolibc. An architecture-matched musl libc SONAME supplies a musl
hint for ET_DYN. Other files without a recognized interpreter report un-
known. It does not model symbol lookup order or plugin loading; its output
is not a dependency resolution or package ABI verdict.
A PT_NOTE GNU build-id note is reported as build-id in lowercase hex when
the file states one, and omitted otherwise. A second note of the same kind,
an empty note and a note longer than 32 bytes are not identities this
reader states, so a file carrying one of them is rejected. elf FILE
--build-id reads the note section instead of the loadable segments, which
is how a separate debug file is checked after a split.
The local scan command stages a regular .holy input in a private file, ver-
ifies that copy, then copies ELF payload files to unnamed temporary files.
It reports each file's class, machine, runtime hint and ISA using the same
reader. It does not execute payloads. It rejects a machine mismatch with
the package arch, including ELF in noarch, and a recognized glibc/musl in-
terpreter that disagrees with the libc tag. For ET_DYN without an inter-
preter, an exact DT_NEEDED libc.so.6, including the basename of an absolute
path, is evidence for glibc and rejects a non-glibc package tag. Architec-
ture-specific ld-musl and libc.musl names provide musl evidence; a bare
libc.so remains unknown. The matching x86/x86_64 ld-linux SONAME together
with a GLIBC_PRIVATE version definition supplies glibc loader evidence; its
bytes are not executed to classify it. An unclassified ELF causes scan to
fail instead of accepting an arbitrary package libc tag. ET_DYN without an
interpreter is not classified as nolibc. Static ET_EXEC is classified only
without PT_DYNAMIC or DT_NEEDED. These checks do not prove plugin require-
ments or resolve dependencies. For each ELF it also prints SONAME,
DT_NEEDED, GNU version needs with weak or required status, version defini-
tions and dynamic symbols with the fields described above, when present.
These file-scoped facts do not prove that a provider exists or that a ver-
sioned symbol resolves.
The preview command stages and verifies a native archive, checks ELF ABI
facts, preflights extraction and inspects each DATA path in a supplied root
by directory descriptor. It reports new paths, existing directories and
conflicts without writing to the root. Parsed HOLY/deps requirements count
as decisions still needed. It counts observed DT_NEEDED edges separately,
including ones omitted from HOLY/deps; it does not deduplicate or select
providers. It also counts executable payload files beginning with #! as un-
resolved script-interpreter edges. It does not parse env indirection or
verify that an interpreter exists. For each #! payload it reports the in-
terpreter path from a bounded header read, or unknown for an incomplete
header. Simple /usr/bin/env and /usr/bin/env -S forms also show a helper-
command candidate. Quoted, escaped or option-heavy env arguments report un-
known; no helper's existence is inferred. Any of these counts requires res-
olution. Preview returns decision-required for nonempty HOLY/hooks and re-
fuses privileged payload modes. A zero-conflict preview is not an instal-
lable plan: it has no installed database generation, dependency resolution,
ownership decisions, transaction journal or apply command. Changes to the
root after preview invalidate the result.
The cache stage command copies a verified local archive to
ROOT/var/cache/holypkg/objects/sha256/SHA256.holy. It validates known ELF
ABI facts and the supported HOLY/deps and HOLY/provides subsets before pub-
lication. It opens cache directories relative to the target root without
following symlinks and rejects a same-name object with a different digest.
Existing target cache directories must allow writes by the caller and must
not be group- or world-writable; each directory must belong to root or the
caller. The command creates cache directories and the object, not installed
state or payload files; it does not resolve providers or run hooks. The
cache remains local and unsigned.
Cache verify opens the object named by a lowercase SHA-256 through the same
target-root directory chain, stages a private snapshot and checks the di-
gest, payload, known ELF ABI facts and supported requirements. It reports
the verified name, arch and libc. It does not mutate the cache or prove
that other package dependencies can be satisfied. cache list reads SHA-256
objects in name order and reports size and current installed-slot status.
It also shows hashes marked unavailable after explicit deletion. It re-
quires an intact database and does not verify payload bytes. cache clean
SHA256 prints a read-only deletion preview. --yes removes the exact object
after rechecking it under the database writer lock and cache lock. The pre-
view reports whether an installed instance or retained transaction refers
to the hash. Such a deletion requires both --yes and --accept-unavailable.
The manager records the hash as unavailable; staging the same verified ob-
ject clears that marker. An incomplete transaction blocks deletion. Un-
known cache entries, unsafe file types and transaction records too large to
inspect cause an error instead of deletion. The command does not remove in-
stalled files or rewrite transaction history.
Database init creates an empty ROOT/var/lib/holypkg with installed, trans-
actions and index directories, and a generation file containing 0. It
locks the database directory for this operation and uses a synced temporary
file plus link for exclusive generation creation; repeating init retains
the existing generation. Init checks existing records and generation before
publishing a new generation file; unknown entries cannot turn a partial
database into an apparently initialized one. Status holds a shared lock
while validating the layout and reading the generation. It recognizes in-
stalled instance directories and one pending reservation. Init rejects any
nonempty installed, transactions or index directory. Both commands reject
symlinked, group-writable or world-writable ancestor directories inside the
target root. Init does not register packages or install files.
Database reserve checks a verified SHA-256 object in the target-root cache,
locks the database at its current generation and publishes one fsynced pre-
pared reservation in transactions/pending. Status validates the record and
returns 5 while it is present. Reserve refuses a second pending record with
status 5. Database cancel validates and removes a prepared or approved
record; it leaves the cached archive and rootfs unchanged. The record has
format holy-reservation-1, its stage, generation and artifact digest. An
approved record also contains the plan digest. This is a reservation of
inputs, not a dependency-resolved install plan or committed transaction.
Incomplete file mutation requires manual inspection. An unknown or mal-
formed journal entry remains an error; cancel will not discard it silently.
Status --json emits one holy-db-status-1 state event with numeric genera-
tion and pending null or a prepared/approved SHA-256 object. An approved
object also includes its plan digest. A malformed database emits one in-
valid-state event and returns 1, without partial state on stdout. A valid
file-mutation journal produces an incomplete event and exit code 5. Status
checks the installed record layout; it does not replace holypkg check's
comparison of installed manifests against the actual rootfs.
Database recover handles interruption of reservation publication. It ac-
cepts one .holy-tmp- followed by 32 lowercase hex digits, verifies its
record against the current generation and removes that directory entry. If
pending also exists, the temporary name must be a hardlink to that exact
pending inode. A valid unpublished approved record for the same artifact
may instead be removed while leaving its prepared predecessor intact. Re-
cover preserves pending and returns 5. With no pending it returns 0 after
removing the orphan. Unknown entries, stale generations, unrelated inodes
and malformed records fail without deletion. For an incomplete applying
transaction, recover returns 5 unless --abort-empty proves that none of its
payload files or installed instance has appeared. --finish-apply clears a
completed applying transaction after validating the new generation and in-
stalled payload. For an incomplete removing transaction, --continue veri-
fies and removes any remaining listed files. It does not replay hooks or
undo installed files.
Database preflight holds a shared database lock, validates the one pending
reservation, reads a verified private snapshot of its cached artifact and
runs the same target-root collision and dependency preview as preview lo-
cal:FILE. It returns 0 for a zero-decision preview, 3 for unresolved re-
quirements or runtime edges, 4 for path conflicts and 6 if the reservation,
cached input or supported payload profile is unavailable. The output names
the database generation and artifact digest. It does not turn the reserva-
tion into an approved plan. File changes outside the database lock can in-
validate the preview before apply; apply rechecks paths and the artifact
under its writer lock. With --json, both preview and database preflight
emit holy-preview-1 path events and a summary only after the full archive
walk. Path events include state new, existing-dir or conflict and optional
interpreter and helper fields. A completed database preflight adds a reser-
vation event with generation and artifact. A hook decision emits a deci-
sion-required error event; database preflight also emits its reservation
event. Invalid input and unavailable reservations emit a structured error
event without partial path output.
The repository index command validates regular .holy files in DIRECTORY and
writes an unsigned, lexicographically ordered index through an atomic re-
name. It refuses symlinked packages, duplicate package identities and a
nonregular existing index. It rejects unsupported HOLY/deps records.
Records in format holy-index-prototype-8 contain the quoted name, version,
release, os, arch, libc, relative filename, SHA-256, size and version com-
parator family. The last field is - when the artifact declares no compara-
tor. The reader checks this field against the chosen archive. Following
package records include typed claim lines bound to the package SHA-256,
copied from validated HOLY/provides. Readers compare every indexed claim to
the artifact before returning records. A separate coverage line declares
complete native payload file coverage. File records contain the package di-
gest and quoted nondirectory path from the verified manifest; readers com-
pare them to the artifact. A separate coverage line declares complete na-
tive package dependency coverage. Requirement records contain the package
digest and all ten validated HOLY/deps fields. Readers compare these
records to the artifact. ELF-derived requirements still require artifact
inspection. A separate coverage line declares complete DT_SONAME facts from
scanned ET_DYN files, with exact name, architecture, libc class and path.
repo providers DIRECTORY soname NAME uses these facts, ignoring unsupported
or forged SONAME claims. A further coverage line and elf-version records
bind defined version names to each library path. When an ELF consumer has
strong version needs for a DT_NEEDED name, indexed closure staging selects
only same-ABI SONAME candidates carrying every required version. For older
indexes, strong symbol imports trigger a targeted scan of each matching
candidate; a library with the right version name but without the requested
export is excluded. Generation 7 indexes exported dynamic symbols per li-
brary path, including version, type, binding, visibility and hidden flag.
Provider search uses these facts to reject a missing export without opening
the candidate archive. It compares indexed exports against the selected ar-
tifact before staging. The resolver checks selected archives again. Old
holy-index-prototype-1 through -7 generations remain readable, with the
coverage each generation declares; optional signatures are sidecars to a
sealed generation. Source adapters must not treat it as the native reposi-
tory format. repo requirements reads one unambiguous package's indexed
HOLY/deps records from the pinned catalog generation without opening its
payload. It returns 6 when the index lacks complete dependency coverage and
3 when name alone is ambiguous. The digest-pinned index is metadata; pack-
age installation still verifies the downloaded artifact and scans its ELF
files.
The seal command validates the entire local index, creates an index.SHA256
generation and atomically publishes a current pointer containing its di-
gest. The list and search commands require current and check its digest
before reading the generation. Indexing creates a draft index; publish it
with seal after reviewing changes. Removing current makes the directory un-
available to list and search rather than exposing an unsealed draft. With-
out --key, the pointer and artifacts remain unsigned. With --key, seal re-
quires an Ed25519 PEM private key, signs the exact index generation and
publishes its 64-byte signature.SHA256 file before changing current. Exist-
ing signatures for the same generation must verify under the supplied key;
seal does not replace them. Existing generation files remain in place.
repo verify requires an Ed25519 PEM public key. It checks the selected in-
dex digest, the signature over the exact index bytes and the referenced
package artifacts. A missing or changed signature, wrong key or altered in-
dex returns 4. The private key stays on the publishing machine. With --pub-
lic-key, repo mirror downloads the signature over HTTPS and verifies it
against the pinned index before downloading packages. It records
ed25519-pinned-key in mirror-origin together with SHA-256 of the raw public
key; a failed signature leaves the new mirror unpublished. Source sync en-
forces a public key frozen by source plan when one is registered. Verifica-
tion establishes authenticity only relative to the supplied key; it does
not reject an older signed generation selected by a changed current
pointer. Publishers must distribute the public key through a trusted chan-
nel and retain separate generation or hash pins where freshness matters.
The repository list command reads that prototype index with the holy.conf
lexer and checks each referenced regular archive's size, SHA-256, identity,
payload and known ELF ABI facts before printing records. An index without a
signature carries no provenance guarantee. Neither command installs pack-
ages or activates this directory as a package source.
The repository search command verifies the same complete local index and
returns exact name matches. repo search-file takes an absolute path and re-
turns matching package records plus coverage status, index hash and time-
stamp. With a version-3-or-newer index, it verifies only indexed candi-
dates; a changed unselected archive does not invalidate this query. Legacy
generations return unavailable coverage and status 6. No match in a com-
plete native index prints a zero-count summary. This prototype does not
perform dependency resolution or provider selection. --fuzzy emits ranked
hints, capped at 20 displayed rows. A hint is not an exact capability
match.
Repository providers searches the sealed local catalog for an exact KIND
and NAME match. It finds indexed claims from HOLY/provides and the pack-
age's own metadata name for KIND=package. With a version-5 index, KIND=son-
ame uses scanned ELF facts instead of claims. For prototype-2 and later, it
validates the complete index and verifies only candidate artifacts and
their full claim sets before printing package records and a count. An unse-
lected object can change without invalidating this query; no match de-
scribes the sealed index generation, not every file in the repository di-
rectory. Prototype-1 queries still verify all artifacts. Listed claims are
unverified: an exact name does not prove file presence, ABI compatibility,
version constraints, or successful execution. There is no source ranking or
dependency solver yet; the prototype-2 query uses indexed claim lookup but
still scans all index rows.
The make check-solver target (also run by make check) exercises an internal
libsolv interface with normalized, exact capability IDs and AND/OR, con-
flict and cyclic fixtures. The caller must pre-filter candidates using
each source family's version semantics before it can use that interface.
The fixture is not a holypkg solve command or an installed-system test. A
missing libsolv development package makes the target fail rather than re-
porting a passed fixture. The internal unique-solution check returns deci-
sion-required when excluding a selected provider permits a second solution.
It does not rank sources or ask the user for a choice. This detects differ-
ing package sets, not multiple provider edges within one set. A plain solve
returns one proposed set, not a reviewed installation plan. With --json it
emits holy-repo-candidates-1 candidate events (package identity, filename,
SHA-256 and byte size), then a count summary. Invalid catalogs emit one er-
ror event without partial candidates; invalid KIND/NAME emits an invalid-
query event. The JSON candidate identifies an artifact, not a selected
provider edge or an ABI compatibility verdict.
The repository fetch command requires a lowercase SHA-256 from the sealed
index. It verifies the complete index and artifacts, then copies the se-
lected snapshot to an existing output directory as SHA256.holy. It rejects
a missing digest or a changed unrelated artifact. The copy does not install
or grant trust to the package; repository metadata and objects remain un-
signed.
SOURCE REGISTRATION
source plan reads the explicit configuration and writes a complete proposed
registry to stdout. It reports alias changes, origin changes, additions and
deactivations on stderr. A change to trust, key, parent, family or priority
under the same source ID is reported as policy-change. Review and save the
plan, then supply its SHA-256 to source apply. An existing initialized
database is required. No source command downloads packages or runs package
code.
The plan binds the previous registry digest, installed database generation
and database device/inode. Apply stages the plan, verifies its approved di-
gest and checks those inputs under the database writer lock. It never
rereads config or includes. A changed input, another database or a dis-
carded historical source returns decision-required (3). Malformed proposed
registry data returns 2; I/O or existing-state corruption returns 1; an in-
complete transaction returns 5.
The registry is /var/lib/holypkg/sources under the target root. Its holy-
sources-3 records contain ID, alias, active/inactive state, canonical defi-
nition, trust policy, public-key fingerprint, parent ID, family and prior-
ity. source show ALIAS prints the effective registered definition and pol-
icy for an active alias without downloading its catalog. Key rotation re-
tains the source ID and changes the registry revision. Replacement uses a
private temporary file, fsync, rename and directory fsync. Registry revi-
sions are separate from installed database generations. List prints iden-
tities and status without endpoint definitions.
source catalog bind ALIAS MIRROR verifies the entire sealed mirror, its in-
dex, objects, source-id and exact registered URL. A source with a regis-
tered key also verifies the signature against that key. It then records the
catalog location and index digest in /var/lib/holypkg/catalogs/SOURCE_ID
under the target root. The file is mode 0600 and replaced with fsync and
rename under the database writer lock. The command changes neither in-
stalled packages nor the source registry. A renamed alias with the same
source-id keeps its binding; an inactive or changed source cannot use it.
Queries without --catalog reread the binding and reject a missing or
changed index; each queried or selected object is verified before use. When
the mirror resides inside the target root, the binding records its root-
relative path and survives moving that root. An external mirror retains its
host path and must be rebound after a move. Sync with --output exports a
mirror without changing the binding.
An ID identifies a configured origin; it is not signature evidence. The
current registry retains inactive origins and supports explicit local-arti-
fact associations in set installation and cached slot replacement. The sync
command reads one active registered SOURCE alias under the database shared
lock. For a holy-http source with one URL, --sha256 INDEX_SHA256 mirrors an
explicitly pinned index and all referenced artifacts through the same val-
idator as repo mirror. A registered public key makes the signature manda-
tory and records its hash with the source ID. Without --output, sync pub-
lishes a verified generation under /var/cache/holypkg/cata-
logs/SOURCE_ID/INDEX_SHA256 in the target root and binds it to the active
source. It reuses a valid cached generation and rejects a corrupt one. The
source definition is checked again when binding, so an alias change during
download cannot bind an unrelated source. --output writes a standalone
NEW_DIRECTORY without changing the binding. The URL must be an HTTPS direc-
tory URL ending in slash. It writes the registered source-id in mirror-ori-
gin before sealing current. The snapshot stays in the chosen directory;
repo search, solve and fetch can use it. Sync without --output writes the
target cache and catalog binding, but does not install packages, execute
scripts or migrate sources.
For a registered holy-git source, sync requires both --commit and --sha256.
The commit is a full lowercase 40- or 64-digit Git object ID. Sync clones
the configured URL, checks out that commit in detached state, validates the
sealed native index and every referenced artifact, and records the commit
with the source ID. A registered Ed25519 key also requires a valid index
signature. The verified clone keeps its .git directory so later source
queries can check that HEAD still names the pinned commit. --output exports
the clone without binding it; without --output the normal target-root cata-
log cache and binding apply. The Git executable is a sync-time tool, not a
package query dependency. Git transport credentials must be supplied out-
side the source definition; the source URL and provenance record do not ac-
cept embedded credentials.
Without --sha256, sync reads the remote current pointer over certificate-
checked HTTPS. It accepts only one line of the form "sha256" followed by a
lowercase 64-digit digest and newline. An unsigned source reports decision-
required (3), including that digest, without creating a catalog; --output
can be omitted for this preview. --root defaults to /. A second call with
--accept-unsigned INDEX_SHA256 rereads current and mirrors index.IN-
DEX_SHA256 only if the pointer still matches. A changed pointer requires a
new decision. The selected immutable index may remain usable if remote
current advances during the later download. The snapshot records selection
current-accepted-unsigned in mirror-origin. Both pointer and index remain
unsigned; the confirmation accepts this exact generation without claiming
publisher signature verification. Malformed pointer data returns 4; missing
network or CA inputs and oversized responses return 6. The current transfer
has a 4 KiB response cap, the same HTTPS-only redirect policy and a five-
minute deadline. --sha256 and --accept-unsigned are mutually exclusive. A
source with a registered public key verifies the signed index selected by
current in one call; --accept-unsigned is invalid for it. Cached signed
catalogs and bound mirrors are checked against the registered key before
reuse. Key rotation makes an older binding unavailable until it is replaced
with a generation signed by the new key. Signature verification alone does
not reject replay of an older signed current pointer; use a generation pin
where freshness matters. There is no hidden TTY behavior.
fetch SOURCE:PACKAGE reads a synced native mirror selected by --catalog or
the binding for its active source-id. The active source alias must match
the mirror's source-id and exact URL; the mirror's current index and se-
lected package object are verified before the named package is copied into
an existing --output directory. --extract instead writes a new output di-
rectory. More than one package with the same name requires a choice (status
3); an absent package or inactive source returns 6. The mirror record re-
tains the digest pin or verified key hash. A bound signed catalog is
rechecked against the registered key before source queries.
For an active type apk source, fetch SOURCE:PACKAGE uses a bound APK cata-
log. Specify --version and --arch to select one indexed artifact. A source
with one repository selects it; otherwise specify --repo. Missing selection
returns decision-required (3). The command verifies the catalog binding,
download and package signature according to the source key, then writes the
original APK and selection receipt in a new --output directory. --import
also writes converted converted output. --extract applies only to native
.holy packages. --catalog can name a bound APK catalog; an arbitrary re-
placement fails source binding. Neither form installs files, hooks or de-
pendencies into the target root.
The source registry is read before network transfer, so a later alias
change does not change the snapshot's recorded ID. A source with another
backend, an inactive alias or an untrusted TLS certificate is unavailable
(6). The index digest is an explicit user pin, an accepted unsigned current
value, or a current value whose index was verified by the registered key.
The recorded source-id is provenance for this snapshot, not an installation
authority derived from package metadata. Multi-source dependency resolu-
tion remains unimplemented. Explicit cached replacement uses db plan-up-
date and db apply-update. These commands do not yet implement JSON output.
See holy.conf(5) for identity rules and endpoint restrictions.
ORPHAN PACKAGES
orphan reads a consistent installed-state snapshot under the database's
shared lock. The default root is /. It follows saved consumer-to-provider
edges from every instance marked explicit and reports dependency instances
outside that reachable graph. A dependency cycle with no explicit consumer
is also orphaned. Only each current instance's own edges participate; ref-
erences copied from an older transaction's other consumers do not keep
packages alive.
The command does not remove files, run package code, fetch artifacts or
need the package cache. Each candidate includes its artifact hash, package
name and the reason unreachable-from-explicit. Names in text output use the
metadata lexer's byte escapes. JSON names preserve valid UTF-8. This is
graph analysis, not a payload or application-health check; use db check for
file integrity.
With --json, holy-orphan-1 candidate events contain artifact, name, reason
and generation. A final summary contains generation, installed, explicit,
reachable and orphans counts. Candidates are ordered by artifact hash. Out-
put is delayed until the complete graph has been read and linked, so a
missing provider never produces a partial orphan list. Errors produce one
error event with code and status: invalid-state (1), missing-provider (4),
incomplete-transaction (5), or unknown-installed-graph (6). Legacy in-
stalled entries without saved graphs require migration before this analysis
can prove reachability.
INSTALLED CONFLICTS
conflict reads a consistent installed-state snapshot under the database's
shared lock and reports the capabilities two installed artifacts both of-
fer. The default root is /. It answers a question a set transaction does
not ask: a set resolves one requirement at a time, so it installs cleanly
and still leaves a name two artifacts claim. The command changes nothing,
runs no package code and needs no cache.
Four kinds of claim are read. A package name two artifacts provide is
mixed-providers, a SONAME two artifacts provide is duplicate-provider, a
file path two artifacts declare is duplicate-provider, and a program name
two private trees place in a bin directory is shadowed-path, since the PATH
a run derives from the private trees resolves that name by sort order. A
private program is the last component of a path under /usr/lib/holy/pri-
vate/ARTIFACT-ID/ followed by usr/bin, bin, usr/sbin or sbin, which is the
shape a package's private layout has.
Two providers of one SONAME whose arch or libc records differ are abi-mis-
match rather than a duplicate, since one name is a different capability for
each runtime. The report names every provider with its artifact hash, its
arch and libc record and the capability, so the reader decides which one a
set meant rather than being told one of them is correct. One artifact stat-
ing a capability twice is not a conflict, since there is only one owner.
Text output is one conflict line per finding, naming the kind, the capabil-
ity, the provider count, the reason and every provider, followed by a gen-
eration, installed, capabilities and conflicts summary. Names use the meta-
data lexer's byte escapes. With --json, holy-conflict-1 finding events
contain kind, name, reason and a providers array of artifact, arch, libc
and package, ordered by artifact hash, and the final summary carries gener-
ation, installed, capabilities and findings. Status is 0 when nothing col-
lides, 1 when a conflict was found, 2 for an invalid argument, 5 for an in-
complete transaction and 6 when an instance or graph record is unavailable.
A pending transaction means the installed set is mid-change, so no finding
from it would be a fact.
INSTALLED FILE INDEX
index reads a consistent installed-state snapshot under the database's
shared lock and answers two questions the planner answers internally: which
installed artifact owns a path, and which artifacts provide a name. The de-
fault root is /. With no selector it prints the whole index, one artifact
record followed by the paths it owns and the capabilities it declares, or-
dered by artifact hash, by path and by capability.
A path selector takes an absolute or a relative path, since the installed
manifests record the relative form, and names the artifacts that own it. A
directory is shared and owns nothing, so it is not an index entry. A capa-
bility selector takes a kind and a name and names the artifacts that de-
clare it with the arch and libc each one records, since the same name in
two ABIs is two capabilities. Naming a path and a capability at once, or
one without the other, is a usage error.
A name with more than one provider is a candidate list, and the status is
1: choosing one of them would be a decision this report does not make,
which is what a fuzzy candidate becomes when nothing names the choice. A
path or a capability no installed artifact owns or declares is status 6,
since an absent answer is not a proof that nothing provides it. With
--json, holy-file-index-1 records carry artifact, name, arch and libc, and
the path and capability records carry the artifact they belong to. The fi-
nal summary carries generation, artifacts, paths and capabilities. Status
is 0 for one candidate or the whole index, 1 for several, 2 for an invalid
argument, 4 for an incomplete transaction, 5 for a pending transaction, and
6 for an instance record that is unavailable.
RUN INSTALLED COMMAND
run selects one installed slot by source alias and package name. Use --arch
and --libc when several installed slots share that name. The local alias
selects a package installed without a registered source. A registered alias
can still select its installed package after the source is removed from ac-
tive config. The command after -- must be a bare name in a standard bin
directory, an absolute path in /usr/bin, /bin, /usr/sbin or /sbin, or an
absolute path the installed manifest owns under /usr/lib/holy/private/ARTI-
FACT-ID/ with a bin directory and a plain file name after it. The last form
is how a package whose payload is private names its own entry point. run
checks the installed manifest, including the executable bit and current
payload, before exec.
When the installed manifest owns a matching path under /usr/lib/holy/pri-
vate/ARTIFACT-ID/, run selects it before the public path. run prepends the
selected package's manifest-owned private bin directories to PATH even when
the main executable uses a public path. It passes argv unchanged and re-
turns the program's exit status through exec. Without a path view this is a
direct launcher. It does not replace shell builtins or intercept another
process that directly executes the public path. A private path still re-
quires an installation transform that places and records the file there;
the current package installation flow does not create such a mapping from a
conflicting public path.
With --root, run executes the selected file at that root's host path. It
does not chroot or isolate the process. Dynamic interpreter and absolute
paths use the host's namespace, so a trial root must use a compatible host
or a static program. An unavailable package or command returns 6; an al-
tered payload returns 4. An incomplete transaction returns 5.
--view PUBLIC=PRIVATE maps one selected package-owned regular file or di-
rectory under /usr/lib/holy/private/ onto an existing public path of the
same type. PUBLIC may be under /usr/bin, /usr/sbin, /bin, /sbin or
/usr/lib, or name an existing /app directory. run checks the selected pack-
age manifest and current payload before creating user and mount namespaces.
It makes mount propagation private, bind mounts the selected file, enters
the target root and execs the command with the caller's UID/GID mapping.
Descendants inherit the view; the mount ends with its namespace. No host
mount or setuid launcher is created. The user namespace maps the caller's
primary UID and GID; supplementary groups are not remapped. If namespaces,
openat2 or mounts are unavailable, run returns 6. An unowned private path,
missing target, symlink target or file/directory type mismatch is rejected.
Directory views require an existing target mountpoint; run does not create
one in the target root. --view changes only the listed paths. With --view
and --root, the command executes inside the target root rather than at a
host-prefixed path.
--auto-view derives regular-file bindings from the selected installed mani-
fest: for a private path ending in usr/bin, bin, usr/sbin, sbin or usr/lib,
it binds that package-owned file over an existing matching public file. It
skips absent public paths and leaves directory views to explicit --view.
Explicit --view entries take precedence. Two private files claiming the
same public path require an explicit choice and return decision-required
(3). The same namespace, ownership and payload checks apply; the option
does not alter other processes or persist a provider switch.
AppImage staging
appimage inspect accepts a type 2 ELF image, checks its x86 or x86_64 ma-
chine type, locates one plausible SquashFS superblock and prints its offset
and SHA-256. The command reads the image without executing its runtime.
appimage extract copies the exact image to a new private output directory
and invokes unsquashfs with the recorded offset. It requires an unprivi-
leged user and an available unsquashfs command. The output contains origi-
nal, AppDir, a classification report and a conversion receipt with state
extracted-unclassified. The report lists ELF arch/libc/ISA, interpreter,
DT_NEEDED, loader search paths, strong symbol versions, shebang scripts,
links that need a path view and unknown executables without following sym-
links. It reports the AppRun entrypoint and runtime probes that static in-
spection cannot close. No .holy is produced and no package is installed.
This operation does not claim that the extracted program can run.
import INPUT --source NAME --format appimage --output NEW_DIRECTORY uses
the same extraction, records NAME as unverified provenance and then emits
one native package beside the review bundle. The package name is the image
file name without its .AppImage suffix, and a name the manifest cannot
record is refused with status 2 rather than rewritten. The version is read
from the desktop entry the image ships, X-AppImage-Version or Version; a
payload that states none records version 0 and the report says so. The arch
and libc come from the payload's own ELF files, and a payload that mixes
architectures or runtimes is refused with status 3, because one .holy
records one of each.
The whole AppDir travels under /usr/lib/holy/private/NAME/appdir/, so no
public path is claimed for the image's own files. The entry point is reach-
able as /usr/lib/holy/private/NAME/usr/bin/NAME.appimage, a link to
../../appdir/AppRun, and /usr/bin/NAME is a launcher that starts that file
in the package run context with holypkg run SOURCE:NAME. When the image
ships an app tree the launcher adds a path view from /app, and only when
the target has /app. The launcher refuses to run as root because the con-
verted payload is no sandbox. The desktop entry is carried to
/usr/share/applications/NAME.desktop with Exec and TryExec rewritten to the
launcher, and the classification and conversion reports travel with the
package as /usr/share/holy/NAME/. Icons are not installed by this package
and no desktop database hook is declared; the distribution owns the cache.
Dependencies are derived from the payload. A DT_NEEDED the payload itself
carries through its own SONAME is satisfied privately and names no require-
ment; every other name becomes one soname requirement. A link with an ab-
solute or escaping target cannot travel in a package payload, so the path
it named becomes one file requirement and the report counts it. Every entry
is attributed to the installing user, and each directory an entry sits un-
der is declared by the same manifest.
import returns status 3 after writing the output, because the package
changes the launch conditions and carries no image-level sandbox. The re-
port in the output directory names the changed conditions, the dropped
sandbox, the runtime probes static inspection cannot close, the path views
a link needs, the scripts that carry an interpreter and the version deci-
sion. NAME does not grant a registered source identity.
Snap staging
snap inspect accepts a file whose first eight bytes state a little-endian
offset and a size, requires an offset of at least 8, checks that a SquashFS
superblock sits there and prints type, offset, compression and SHA-256. A
superblock states no machine class, so the architecture comes from the pay-
load's own ELF files. The command reads the image without executing its
runtime.
snap extract copies the exact image to a new private output directory and
invokes unsquashfs with the recorded offset. It requires an unprivileged
user and an available unsquashfs command. The output contains original, the
snap tree, a classification report and a conversion receipt with state ex-
tracted-unclassified. The report lists ELF arch/libc/ISA, interpreter,
DT_NEEDED, loader search paths, strong symbol versions, shebang scripts,
links that need a path view and unknown executables without following sym-
links. It reports the command the manifest names and runtime probes that
static inspection cannot close. No .holy is produced and no package is in-
stalled. This operation does not claim that the extracted program can run.
import INPUT --source NAME --format snap --output NEW_DIRECTORY uses the
same extraction, records NAME as unverified provenance and then emits one
native package beside the review bundle. The package name and version are
the name and version the manifest declares; a name the package format can-
not record is refused with status 2 rather than rewritten, and a manifest
with no readable meta/snap.yaml or no command is refused with status 3. The
arch and libc come from the payload's own ELF files, and a payload that
mixes architectures or runtimes is refused with status 3, because one .holy
records one of each.
The whole snap tree travels under /usr/lib/holy/private/NAME/snap/, so no
public path is claimed for the image's own files. The command the manifest
names has to sit inside that tree; a command that names a path outside it
is refused with status 3. The entry point is the snap image itself at
/usr/lib/holy/private/NAME/usr/bin/NAME.snap, and /usr/bin/NAME is a
launcher that starts that file in the package run context with holypkg run
SOURCE:NAME. The launcher refuses to run as root because the converted pay-
load is no confinement. The classification and conversion reports travel
with the package as /usr/share/holy/NAME/.
Snap semantics that this format cannot keep are recorded, not emulated. The
base the manifest names is a runtime snapd would mount from a snap store,
so it becomes one package requirement on that name and the target needs a
source that provides it; a superblock states no runtime, so the requirement
names no libc. The plugs, the confinement, the system usernames and the
layouts are dropped and counted, the hooks are recorded because nothing
runs them at install, and the environment names, the command-chain entries
and the part stage-packages are kept as x- records with a report line each.
A link with an absolute or escaping target cannot travel in a package pay-
load, so the path it named becomes one file requirement.
import returns status 3 after writing the output, because the package
changes the confinement and needs a base package no snapd provides. The re-
port in the output directory names the base requirement, the dropped con-
finement and interfaces, the recorded hooks, the command-chain and environ-
ment records, the path views a link needs and the scripts that carry an in-
terpreter. NAME does not grant a registered source identity.
Scoop artifacts
import INPUT --source NAME --format scoop --output NEW_DIRECTORY reads one
Scoop manifest as JSON text and writes a conversion directory holding the
original manifest, one native package and a package report. The manifest
file name is the package name, as a bucket names an app, and a name the
package format cannot record is refused with status 2 rather than rewrit-
ten, as is a manifest with no literal version, no artifact URL or no
SHA-256 digest.
The artifact has to sit beside the manifest, since nothing runs to fetch
and unpack it, and its SHA-256 must be the digest the manifest pins; a dif-
ferent digest or an absent file returns 6. An artifact libarchive reads has
its members placed under /usr/lib/holy/private/NAME/, and the directory the
manifest declares as extract_dir replaces the first component of every mem-
ber path. Any other artifact travels as one file. A member with an absolute
or escaping path, or of a kind a payload does not carry, is refused or
counted and reported. The program the manifest names is looked up inside
the payload and recorded as x-scoop-program when it is there.
A Scoop dependency names an app of the same bucket, so each one becomes a
package requirement the target has to satisfy from a source that has it.
The installer, pre_install, post_install, uninstaller, env_add_path,
env_set, persist, shortcuts, psmodule and suggest keys are dropped and
counted, because a PowerShell step and a Windows integration belong to a
Windows installation; checkver and autoupdate are counted too, since a
bucket owns its own index. Nothing in the artifact is executed, and no Wine
requirement is recorded, because a package cannot promise a runtime this
manager does not provide.
import returns status 3 after writing the output, because the payload is a
program for another operating system. The report names the artifact and its
digest, the directory and program the manifest states, the dropped in-
staller and integration keys, the requirements a bucket dependency became
and any archive member the payload refused. NAME does not grant a regis-
tered source identity.
WinGet artifacts
import INPUT --source NAME --format winget --output NEW_DIRECTORY reads one
winget-pkgs manifest as YAML text and writes the same conversion directory
a Scoop manifest produces: the original manifest, one native package and a
package report. PackageIdentifier is the package name and PackageVersion
the version; a name the package format cannot record is refused with status
2 rather than rewritten, as is a manifest with no literal version, no In-
stallerUrl or no InstallerSha256. A WinGet digest is written in upper case
and is compared in lower case, so both forms verify.
The artifact, the extract_dir and the counted keys are the shared artifact
rules the Scoop section states, so a WinGet package carries the verified
artifact under /usr/lib/holy/private/NAME/ as well. A manifest that de-
clares no program path records none, and the report says so rather than
guessing one from the archive.
A PackageDependencies entry is the only nested block this converter reads,
because it names the packages the manifest requires; every other nested
block is skipped and its key counted. The installer keys, the Windows inte-
gration keys and the catalog keys are dropped and counted the way the Scoop
report counts them, and the catalog is named as a WinGet catalog rather
than a bucket. Nothing in the artifact is executed and no Wine requirement
is recorded, so import returns status 3 after writing the output.
Nix closures
import CAPTURE --source NAME --format nix --output NEW_DIRECTORY reads one
captured closure as text and writes a conversion directory holding the cap-
ture, one native package per store path and a closure report. A store path
is a 32-character Nix base32 hash, a hyphen and a name, with or without the
/nix/store prefix, and a store path name the package format cannot record
is refused with status 2 rather than rewritten. Two store paths of one name
would need one rewritten package name, so the capture is refused with sta-
tus 2 as well.
The capture is one record per line: path STORE_PATH declares a store path
of the closure, root STORE_PATH names the output root, entry STORE_PATH
PATH names the program a store path starts, and references STORE_PATH
STORE_PATH... declares the store paths one path needs. A path or a root is
named once; a reference to a store path the capture does not declare loses
a closure member, so it is refused with status 2. A record name this con-
verter does not model is counted and reported, since an unread reference is
a requirement nobody counted.
Every store path has to sit beside the capture as a directory named for it,
because nothing runs to build or fetch a store, and an absent one returns
6. Each store path travels whole under /usr/lib/holy/pri-
vate/NAME/store/STORE_PATH/, and one pass over its own bytes confirms the
references the capture declares: a reference the payload carries is con-
firmed and a reference it does not is counted, because a store records the
references itself and a payload cannot prove that a store path has none.
A reference to a store path the capture carries becomes one package re-
quirement on that name, so two applications that name the same store path
share one package with one owner and several dependents, and removing an
application leaves it. A reference to a store path the capture does not
carry becomes a requirement on that name whose original field is the store
hash, and the target needs a source that provides it; the requirement
states no architecture and no runtime, since the store path that would
state one is the one the capture does not carry. Nix states no version, so
every package records version 0 and the store hash is its identity,
recorded as x-nix-store-hash.
A store path that states an entry point gets a link at /usr/lib/holy/pri-
vate/NAME/usr/bin/NAME and a generated launcher at /usr/bin/NAME that
starts it in the package run context with holypkg run SOURCE:NAME. A store
path that names no entry point claims no public bin path, and the report
says so rather than guessing one. An entry point that is not an executable
file of its own store path is refused with status 3.
Nothing is executed. No Nix store, daemon, profile or sandbox is repro-
duced, and a program that opens an absolute /nix/store path needs a store
view this manager does not build, which the report and the transform record
name. import returns status 3 after writing the output. NAME does not grant
a registered source identity.
Solus eopkg
import FILE.eopkg --source NAME --format eopkg --output NEW_DIRECTORY reads
one Solus binary package and writes a conversion directory holding the ar-
tifact, one native package and an eopkg report. An eopkg is a ZIP carrying
metadata.xml, files.xml and an install tar. The metadata is required and
the install tar is required, and an artifact missing either is refused with
status 2. A member this reader does not model is counted rather than
dropped, and a member that is not a regular file is refused, since an eopkg
is a set of regular files.
The metadata is read as XML text. A value lands in its own context, so a
Name under Source is a packager identity and the Name under Package is the
package name, and a dependency under BuildDependencies is not a runtime re-
quirement. The fields carried are the name, the version, the architecture,
the summary, the license, the source URL, the component PartOf names, the
PiSi format and each RuntimeDependencies entry with the distribution re-
lease its releaseFrom attribute states. Solus states no release of its own,
so the native release is 1. A metadata without a name, a version or an ar-
chitecture is refused with status 2, and a document that is not well formed
or that ends inside an element is refused the same way. An element this
reader does not model is counted: a description, a packager, a history en-
try, a conflict, a replacement and a declared capability are all named in
the report and none of them becomes a requirement.
The architecture decides the placement. noarch, x86_64, i686, aarch64 and
armv7hl name an architecture this manager places, and anything else needs
classification, so the import returns status 3 without writing a package.
The install tar travels whole under /usr/lib/holy/private/NAME/eopkg/. A
Solus package owns a system layout, and this manager records that layout
rather than claiming it, so the payload installs under a private path and
claims no public bin path. A leading ./ names the same path and is dropped;
a member that names .. is refused rather than placed, and the conversion is
refused with status 1. A payload that is not an archive this reader can
read is refused the same way. A member that is neither a regular file, a
directory nor a symbolic link is skipped and counted, and a symbolic link
whose target leaves the private tree becomes a recorded file requirement on
the path it named.
Each runtime dependency becomes one exact package requirement on the pack-
age name, with any architecture and any runtime, since the dependency names
a repository member and not a file the payload can place. The original
field is the releaseFrom value when the metadata states one and - when it
does not, because a distribution release is a property of the repository
rather than of the dependency. A SONAME the payload needs and does not pro-
vide becomes a SONAME requirement, and a package requirement on the package
name is recorded as well.
Nothing is executed. An install script, a COMAR object, a delta, a signa-
ture and a declared file list are named in the report and dropped: a COMAR
object is a lifecycle ABI this manager does not provide, a delta is a dif-
ferent payload rather than a patch, and a signature is recorded without be-
ing trusted, so the artifact is verified by its own digest only. import re-
turns status 3 after writing the output. NAME does not grant a registered
source identity.
Split proposal
holypkg split TREE --output NEW_FILE [--split OUTPUT GLOB ...] reads one
prepared package tree, the HOLY and DATA shape that manifest generate and
pack consume, and writes a proposal naming the split outputs its payload
needs. It changes nothing, executes nothing and installs nothing; the pro-
posal is what a holy-recipe(5) manifest states in its output and split
records.
Every non-directory path receives exactly one output, and the proposal
names the three the tree may use: the package name as its runtime output,
NAME-devel and NAME-doc. A development or documentation output is declared
only when a path is assigned to it or a decision suggests it, because a de-
clared output that receives nothing is what a build rejects. A path line
records the path, its output and the reason, so an operator can overrule
the heuristic.
An explicit --split rule outranks a heuristic: the first rule that matches
a path assigns it, and two rules naming different outputs for one path
leave that path a repeated decision rather than a guess. A rule may only
name one of the three outputs the proposal declares, and a rule naming any
other output is refused with status 2.
The heuristics are deliberately weak, because a file extension is not a
sufficient criterion. A header, an include directory and pkg-config meta-
data are proposed for the development output. A shared object carrying a
version stays in the runtime output, and a man page, an info page or a ref-
erence document is proposed for the documentation output; a license and a
runtime data file are not documentation, however a general pattern would
match them, and they stay in the runtime output.
Four cases are decisions rather than assignments, each reported with the
output a heuristic would have chosen and the reason. An unversioned shared
object is a decision: the payload may open it through dlopen at runtime,
and a tree states no metadata or probe that settles that. A static archive
is a decision: it is development material to a builder and a link input to
nothing at run time, and only the project knows which. A link whose target
leaves the tree or is absolute is a decision, since a payload carries nei-
ther, and two rules contradicting each other are a decision by definition.
The command prints the path counts per output and one decision line per un-
settled path, and returns status 3 when any path is a decision. A tree
without HOLY/meta or without a name the output names may be derived from is
refused with status 2, and a tree that is not there returns 6. A device,
socket or fifo is refused, because a payload carries none of them.
--debug adds a NAME-debug output whose files are cut from the runtime files
with objcopy. Each debug record names the runtime path and the build-id
note both files keep, so a stripped artifact and its debug file are matched
by an identity rather than by a name, and a debug record states the tool
that cuts the pair: objcopy --only-keep-debug on the runtime file, then ob-
jcopy --strip-debug --add-gnu-debuglink on the artifact that installs. An
ELF that states no build-id is a decision rather than an untraceable debug
entry, because nothing then ties a debug file to the artifact it was cut
from. A file this reader cannot parse as an ELF is refused, since --debug
reads only the ELF payloads of a tree.
holypkg elf FILE --build-id prints the GNU build-id note of one file in
lowercase hex, reading the note section rather than the loadable segments.
A separate debug file keeps the note and loses the segments a loadable ar-
tifact needs, so the strict elf reader refuses it and this form still an-
swers. A file that states no note, or is not an ELF, returns 2.
Recipe conversion
holypkg convert PKGBUILD --source NAME --output NEW_DIRECTORY reads one
PKGBUILD as text and writes a conversion directory containing the original
file, a copy of each local source it names, a holy-recipe(5) manifest and a
conversion report. A file named template reaches the Void converter, a file
named APKBUILD reaches the Aports converter, and a name ending in .Slack-
Build reaches the SlackBuild converter, a name ending in .spec reaches the
RPM spec converter, a directory named debian reaches the Debian converter
and a name ending in .ebuild reaches the Gentoo converter and a name ending
in converter, a name ending in .scm reaches the Guix converter and a name
ending in while any other name reaches the pacman converter, and import IN-
PUT --source NAME --format pkgbuild|void|aports|slackbuild|rpmspec|de-
bian|gentoo|pacstall|flatpak|homebrew|guix --output NEW_DIRECTORY names the
family explicitly. None of these commands installs a package, builds one,
or grants NAME a registered source identity; the report records it as
provenance.
The PKGBUILD converter carries pkgbase, pkgver, pkgrel, pkgdesc, url, li-
cense, arch, epoch, depends, makedepends, checkdepends, optdepends and
backup, expands $pkgbase, $pkgname, $pkgver and $pkgrel inside source en-
tries, and turns a sha256sums entry into source-sha256. A list is read the
way a shell reads it: whitespace separates elements and a comma inside an
element belongs to its name. The build, check and package bodies keep their
original Bash with a prologue that maps the makepkg working variables onto
the exported Holy paths, so $srcdir is $HOLY_SRC and $pkgdir is $HOLY_DEST,
or $HOLY_SPLIT_DEST inside a package_NAME split output. An install= frag-
ment is copied into the payload and recorded as one postinstall hook. pro-
vides, conflicts, replaces, noextract, validpgpkeys, options, non-SHA-256
checksums and architecture-specific source lists are preserved as x-
records or reported as unknown. The build environment of makepkg itself is
not reproduced, so a converted recipe reports makepkg.conf and the phase
environments as unresolved helpers.
The Void converter carries pkgname, version, revision, short_desc, home-
page, license, maintainer and changelog, reads distfiles and checksum as
lists, and expands $pkgname, $pkgbase, $version, $revision, $sourcepkg and
$pkgver inside them. The xbps-src phase functions keep their original Bash
behind a prologue that maps $wrksrc onto $HOLY_SRC, $masterdir onto
$HOLY_WORK and $DESTDIR onto $HOLY_DEST, so HOLY_SRC takes the place of the
$wrksrc directory that xbps-src builds. The v* helpers a body calls are
carried into that prologue, and vman, vsv, vsed, vcompletion and vsrccopy
are reported as unresolved. A NAME_package function becomes an output whose
split step carries its pkg_install body, with DESTDIR still the main tree
and PKGDESTDIR the staging tree. A patches or files directory is archived
beside the recipe and named by PATCHESDIR or FILESDIR, and an INSTALL or
REMOVE file becomes one hook. build_options are fixed to build_options_de-
fault, so a $(vopt_with) call becomes its recorded value rather than a
shell helper. A phase the template leaves out is reported as supplied by
common/build-style/NAME.sh, which the converter does not run.
The Aports converter carries pkgname, pkgver, pkgrel, pkgdesc, url, license
and maintainer, maps arch onto the Holy machine names, and reads depends as
depend and makedepends, makedepends_build, makedepends_host and checkde-
pends as build-depend, so a !NAME conflict becomes x-conflicts and a so: or
cmd: requirement is reported. The abuild phase bodies keep their original
shell behind a prologue that maps $srcdir onto $HOLY_SRC, $startdir onto
$HOLY_WORK, $pkgdir onto the main tree and $subpkgdir onto the staging
tree, and the step changes into $builddir because abuild runs a phase
there. Each subpackages entry becomes an output whose split step carries
its split function, with the function taken from the entry or from the last
dash-separated suffix of the name, and an entry with no written function is
reported as needing the abuild default_dev, default_doc or default_static
helper. A post-install or pre-deinstall script becomes one hook. Since
aports pins sha512sums, which a Holy source cannot use, a remote source
with no sha256sums entry is reported rather than fetched, while a local
source beside the APKBUILD is copied and hashed as SHA-256. The default_*
helpers, the abuild phases the template leaves out, the cross and chroot
variables and any top level statement abuild would source as a shell script
are reported rather than run.
The SlackBuild converter carries PRGNAM, VERSION, BUILD, TAG and PKGTYPE,
reads the summary, homepage, requires and conflicts out of slack-desc, and
reads the upstream URL and the MD5 sum out of the info file. A SlackBuild
script is one linear shell program, so it becomes a single build step whose
prologue maps $ARCH onto $HOLY_ARCH, $CWD and $TMP onto $HOLY_SRC, $PKG
onto $HOLY_DEST and $OUTPUT onto $HOLY_OUT. The cd $PKG and the
/sbin/makepkg call are left out because the engine packs the payload, and
so is a tar line that reads $CWD, because the engine has already unpacked
the recorded archive. Every other file the script reads through $CWD trav-
els beside the recipe as a source, a doinst.sh becomes one hook, and this
format pins an MD5 sum rather than a SHA-256 digest, so a remote archive is
reported unless a local copy is beside the script. The strip pass, the own-
ership rewrite, the user and group creation and the loader and desktop
cache helpers are reported as unresolved.
The RPM spec converter carries Name, Version, Release, Summary, License and
URL, collects the %global and %define records, and expands the macros it
can resolve, so a body keeps its own words with _sourcedir, _builddir and
buildroot rewritten onto the exported paths. BuildRequires becomes build-
depend and Requires becomes depend, read one record per line so the compar-
ison travels with the name, while Provides, Conflicts, Obsoletes and Recom-
mends become x- records because none of them is a requirement. The prep,
build, install and check sections become the matching phases, %setup, %au-
tosetup and %autopatch become a cd into the unpacked tree and a patch pass,
and every other rpm section command is reported and left in the body so the
build fails visibly. A %package block becomes an output whose split step
copies the paths its own %files list named out of the main tree. A local
Source or Patch file beside the spec is copied and hashed as SHA-256; one
that is absent is reported, since a spec normally pins no digest.
The Debian converter reads debian/control and takes the version from the
first record of debian/changelog, so the distribution version is what the
recipe records. The Debian revision becomes the Holy release, an epoch is
preserved and dropped, and a native package with no revision gets release
one. A relationship field is a comma separated list of groups and a group
may offer alternatives with a pipe, so the first alternative of each group
is carried and the rest are reported, while Provides, Breaks, Conflicts,
Replaces, Recommends, Suggests and Enhances become x- records. A dpkg sub-
stitution variable such as $ {misc:Depends} is reported rather than
guessed. Each binary stanza becomes an output, and a binary that names a
.install, .docs, .manpages or .links file list becomes a subpackage whose
split step copies the paths that list named out of the main tree, while the
binary that names no list owns the whole tree. debian/rules becomes the
build step behind a prologue that sets DEB_HOST_MULTIARCH and DEB_BUILD_OP-
TIONS, and every debhelper call, every debhelper override and dpkg-build-
package are reported and left in the body so the build fails visibly. The
maintainer scripts, debian/copyright, debian/watch and debian/source/format
are copied next to the recipe.
The Gentoo converter takes the identity from the file name, which is PN-PV-
rPR.ebuild, so a trailing -rN becomes the Holy release and a file name with
no revision gets release one, and an epoch is preserved and dropped. EAPI,
LICENSE and SLOT are carried, and KEYWORDS names the machines an ebuild is
tested on rather than the machine that builds it, so arch is any. A depen-
dency atom is a category, a package and an optional comparison, so the cat-
egory is dropped, a blocker becomes x-conflicts, and a version keeps its
upstream part with the same comparator names the PKGBUILD converter uses. A
USE conditional, an any-of group, a slot, a use dependency and a virtual
are reported, since a Holy recipe has no USE flags and cannot choose be-
tween the members of a group, and the atoms inside one are carried anyway.
A mirror:// entry and a remote archive are reported, because a Gentoo Mani-
fest pins a BLAKE2B rather than the SHA-256 a Holy source needs, while a
PATCHES entry and any other file named beside the ebuild is copied next to
the recipe and hashed. Each standard phase function becomes the matching
phase behind a prologue that rebuilds EPREFIX, ED, D, DESTDIR, WORKDIR,
DISTDIR, T, PN, PV, PR, PF, P and PVR from the exported Holy paths, and
every eclass and every ebuild.sh helper a body calls is reported rather
than run, so the build fails visibly rather than losing a step.
The Pacstall converter carries pkgname, pkgver, pkgrel, pkgdesc, url, li-
cense, maintainer, repology, arch and gives, and expands $pkgname, ${pkg-
name}, $pkgver, $pkgrel, $gives, $pkgbase and $epoch inside a value, as
well as a variable the same pacscript states in an assignment of its own. A
value that needs the shell to choose a substring is a computed identity and
returns 2. An epoch is preserved and dropped, amd64 and x86_64 become
x86_64, i386 and i686 become i686, any and all become any, and a machine
Holy does not carry is written as it stands and reported. A source entry is
NAME::URL, ?NAME::URL or a plain URL, a sha256sums entry in the same order
becomes source-sha256, and a file named beside the pacscript is copied next
to the recipe and hashed; a git address, a remote source with no digest and
a plain http address are reported. depends, makedepends and checkdepends
become depend and build-depend, a group of alternatives written with a pipe
is reported with the first of them carried, pacdeps become depend with the
fact that they name packages of the same pacstall repository reported, and
provides, conflicts, breaks, replaces, enhances, recommends and suggests
become x- records while an optdepends entry becomes x-optdepend. A backup
entry becomes config, and an r: prefix becomes config mutable. Every list
written per machine or per distribution under a suffixed name is reported,
as is every setting that steers a Pacstall run and every digest list other
than sha256sums. prepare, build, check and package become the matching
phase behind a prologue that rebuilds pkgdir, pacdir, srcdir, startdir,
builddir, TARCH, NCPU, pkgname, pkgbase, pkgver, pkgrel, pacname, gives and
epoch from the exported Holy paths; a list of names with a pkgbase is a
split pkgbase whose package_NAME functions become the split steps that fill
each output. Every helper a body calls and every variable of the Pacstall
environment a body reads is reported, and pre_install, pre_upgrade,
post_install and post_upgrade become one install hook while pre_remove and
post_remove become one remove hook, each written beside the recipe and in-
stalled into the payload.
The Flatpak converter reads a JSON manifest. The name is the last dotted
component of its application id, and an id that names a machine and a
branch after a slash keeps that reflex in x-app-id while the branch sup-
plies the machine. The version, summary, url, command, branch and machine
are carried; a manifest that states no version records zero and the report
says so. The sdk becomes a build requirement and the runtime a runtime re-
quirement, because the SDK that builds the modules and the runtime the pro-
gram runs on are two different ids. A module becomes one step in module or-
der: its build-options export env, the flag variables and append-path, its
commands keep their shell, and the make, autotools, autogen, cmake and me-
son templates are replaced by the shell that runs the same tools, which the
report names. A patch applies with -p1 before its module builds, an inline
source becomes a file beside the recipe, a local archive is copied next to
the recipe and a remote one keeps its declared sha256. A /app path inside a
command becomes $DESTDIR and the prefix of a module becomes /usr, so a con-
verted build writes into the payload. finish-args permissions, cleanup
steps, build extensions and a buildsystem with no Holy phase are reported
as dropped or unresolved, and a manifest with several modules is reported,
because a Flatpak build shares one prefix across its modules while each
step here installs into the payload.
The Homebrew converter reads a formula as Ruby text and never evaluates it.
The class name becomes the package name, the description, homepage and li-
cense are carried, the url and its sha256 become one pinned source, the re-
vision becomes the release, and a formula that states no version records
the version its source url carries or zero, with the report naming which. A
depends_on becomes a runtime requirement and a :build dependency a build
requirement; a :recommended or :optional dependency is reported, since a
build cannot promise a feature it did not ask for. A resource or a patch
block that pins a url and a digest becomes a fetched source, and a patch
applies with -p1 in the prepare phase.
A system call inside def install or on_linux becomes a build step that runs
the same command in the source root, and the Homebrew prefix is rewritten
onto the build root: #{prefix} and HOMEBREW_PREFIX become $HOLY_DEST,
#{libexec} becomes $HOLY_DEST/libexec, #{etc} and #{var} become
$HOLY_DEST/etc and $HOLY_DEST/var, and #{buildpath} becomes $HOLY_BUILD. An
interpolation this reader does not model keeps its text and is reported.
Every other statement of that body is Ruby, so the formula is copied beside
the recipe as homebrew-install.rb, the build declares a ruby build require-
ment, and the report names the file and line of each statement: a shell
step that pretended to run Ruby would be a lie. An engine lift puts the ex-
tracted source root where a Homebrew build runs, since the engine extracts
an archive under its own name. A head, a mirror, a uses_from_macos frame-
work, an on_macos, on_arm or on_intel block, a bottle block, a service
block and a test block are reported, because a bottle is a prebuilt foreign
binary, a launchd job has no Linux counterpart and a Homebrew test harness
does not exist here.
The Guix converter reads a package definition as Scheme text and never
evaluates it. The name, version, synopsis, homepage, license and build
system are carried, and a definition that states no version records zero,
which the report names. An origin becomes one pinned source: a url-fetch is
fetched by its uri, a uri written as a string-append becomes the concatena-
tion of its literals, and the base32 digest a definition writes is decoded
into the hex a Holy source records. An origin that is not a url-fetch is
reported, since a git or a local origin is a source this converter does not
fetch. Each name in inputs becomes a runtime requirement and each name in
native-inputs a build requirement; a versioned entry contributes only its
name, since a version constraint is not a name a record can hold.
The build system becomes the tools it runs. gnu-build-system becomes au-
toreconf when the source carries configure.ac or configure.in, then config-
ure with the payload prefix, make and make install; cmake-build-system and
meson-build-system become their three commands; a build system this reader
does not replace, and the trivial one whose procedure is a Scheme body, are
reported. An engine lift puts the extracted source root where a guix build
runs, since the engine extracts an archive under its own name. A #:config-
ure-flags, #:make-flags or #:install-flags argument whose entries are lit-
eral strings becomes the flags of the phase that takes them, and an argu-
ment that computes a value is reported. A #:phases argument, a #:tests?
that turns the suite off, a patch-shebang and any expression this reader
does not evaluate, such as a modulo, are reported.
All of these reports name every carried, preserved, helper, unknown and
changed item with its source file and line range, and their status is na-
tive or review-required. A review-required conversion returns 3 with the
recipe written; a computed identity, a missing identity or malformed text
returns 2, and an unreadable input returns 6.
Prepared source set
add SOURCE:PACKAGE --prepare prints the selected set, source bindings and
plan hash, then returns success without installing files or writing a
transaction. It can stage verified artifacts in the target cache. It cannot
be combined with --yes. The caller must preserve the catalog and artifact
hashes when carrying this selection into another target root.
EXIT STATUS
0 indicates success under the implemented checks; 1 indicates a failed lo-
cal fetch, extraction or check; 2 indicates invalid arguments, configura-
tion or package inspection. Diagnostics go to standard error except --json
check events, which go to standard output.
Preview returns 3 when valid requirements need provider decisions, 4 for
path conflicts and 6 when hooks or privileged modes need unsupported re-
view. It returns 1 on target-root I/O failure.
SEE ALSO
holy.conf(5), holy-package(5)
Holy September 2026 HOLYPKG(8)
holy-recipe.5 source=holy version=development sha256=fc71736aaf42d4756d92187a3c5ca1825374ce0127c222a8af22a73ea20c372f
HOLY-RECIPE(5) Holy package format HOLY-RECIPE(5)
NAME
holy-recipe - Holy build recipe, phases and build environments
SYNOPSIS
format holy-recipe-1
name NAME
version VERSION
release RELEASE
arch ARCH
libc LIBC
summary TEXT
source NAME INPUT
source-sha256 NAME DIGEST
build-depend EXPRESSION [RELATION VERSION]
depend EXPRESSION [RELATION VERSION]
output NAME KIND
split OUTPUT GLOB
config PATH [mutable]
hook-install INTERPRETER PATH
hook-remove INTERPRETER PATH
x-KEY VALUE
step PHASE INTERPRETER [ARG...] <<TAG
TAG
step-file PHASE INTERPRETER [ARG...] PATH
split-step OUTPUT split INTERPRETER [ARG...] <<TAG
TAG
DESCRIPTION
A Holy recipe is a UTF-8 text manifest plus executable steps. The metadata
lines use the holy.conf(5) lexer. No shell expansion, variable substitution
or command substitution is performed on manifest values. An executable
block starts with step PHASE INTERPRETER and a <<TAG marker; every byte up
to a line containing only TAG is the step body. The metadata parser never
executes a block. step-file names a script relative to the recipe file in-
stead of an inline block. An interpreter with arguments is an argv record:
the step runs those words plus the script path, never a concatenated shell
string. split-step names the output it fills instead of a phase, so it may
only appear in the split phase.
Required keys are format, name, version, release, arch, libc and at least
one output. format must be holy-recipe-1. A repeated scalar key is an error
with its line number. Unknown keys are errors; keys with the x- prefix are
preserved in the produced HOLY/meta as one key with its words rejoined into
a single quoted value, since HOLY/meta records one key and one value per
line. A source may be an https URL, an absolute path or a path relative to
the recipe file. A network source needs source-sha256; a local source with
source-sha256 is verified before use and a mismatch is an error. An archive
source is extracted as a compressed tar when libarchive recognizes it, and
a plain file is placed unchanged.
OUTPUTS
output NAME KIND declares one produced artifact. KIND accepts runtime, de-
vel, docs, debug and metapackage. split OUTPUT GLOB assigns payload paths
to that output using fnmatch semantics; the first matching rule wins and
unmatched paths belong to the first declared output. Every non-directory
payload path belongs to exactly one output, and the build fails when a de-
clared output receives nothing.
holypkg split(8) writes a proposal of those records from a prepared tree
without building anything. The proposal names the runtime, NAME-devel and
NAME-doc outputs, records the reason each path received one, and reports a
path it cannot settle as a decision rather than assigning it from the file
extension. An explicit rule in the proposal outranks the heuristic, so a
settled tree yields a manifest an operator can paste and build.
With --debug the proposal also declares a NAME-debug output and one debug
record per runtime ELF, naming the build-id both the stripped artifact and
its debug file keep. The arch, libc, dependencies, provides, files and
hashes of every output are computed from that output's own files, so a doc-
umentation output and an ABI library never inherit one another's metadata.
split-step OUTPUT fills a private staging tree for that output. It runs af-
ter the package phase with HOLY_SPLIT_DEST set to that tree, and every path
it creates belongs to OUTPUT. No split pattern may redirect those paths,
and a split step whose staging tree stays empty fails the build. A require-
ment found in a file, and a hook whose script sits in a tree, are recorded
only in the output that carries that file.
Each output is grouped by the ABI facts of the files it receives. Files
without a recognized ELF class land in the noarch-nolibc group; an ELF
whose machine or runtime cannot be classified fails the build. A payload
mixing several ABIs produces one artifact per group, named
NAME--ARCH--LIBC.holy. A symlink or a data file in a multi-ABI output is
assigned to the first group of that output and reported as ambiguous. A
metapackage output carries no payload and depends on the produced ABI vari-
ants of the same build.
CONFIG FLAGS AND HOOKS
config PATH marks a payload file as config; config PATH mutable marks it as
config,mutable. The packer writes flag none, so build applies the declared
flags to the generated manifest. A path outside the payload is an error.
hook-install and hook-remove record runtime hooks with an absolute inter-
preter and a script path relative to the target root. The script must exist
in the built payload: its SHA-256 is recorded in HOLY/hooks, and the in-
staller reads the body from the installed manifest. No hook runs during the
build.
PHASES
Normalized phases run in this order: fetch, unpack, prepare, configure,
build, check, package, split. A phase without a step is skipped, except
that the manager always fetches and extracts. Each step is a separate
process with its own working directory: build, check, package and split
start in HOLY_BUILD, the other phases in HOLY_WORK. A split step names its
output instead of a phase and always runs after the package phase, whatever
the order of its line.
The manager sets HOLY_WORK, HOLY_SRC, HOLY_BUILD, HOLY_DEST, HOLY_OUT,
HOLY_ARCH, HOLY_LIBC, HOLY_JOBS, HOLY_BUILD_TARGET, HOLY_HOST_TARGET and
HOLY_TARGET to absolute paths in the selected build root. A split step also
receives HOLY_SPLIT_DEST. Fetched sources live in HOLY_WORK/sources. An
archive source is extracted into HOLY_SRC/NAME; a plain file is placed
there unchanged. An unpack step runs after the manager has extracted every
source, so it may move or reshape that content.
BUILD
holypkg build RECIPE --output NEW_DIRECTORY [--environment host|clean|vm]
[--work NEW_DIRECTORY] [--jobs N] [--yes] [--noninteractive] [--keep]
--work selects an existing empty build root; without it a private directory
under /tmp is created and removed after a successful build. --keep retains
a root the manager created and prints its path; with --work the root al-
ready belongs to the caller and is never removed, so the printed line names
the caller instead. --jobs sets HOLY_JOBS. A recipe phase step is shown
with its interpreter, argv, working directory, uid and full body before it
runs; on a terminal the operator answers per step, where a accepts every
remaining step. Without a terminal and without --yes the build stops with
decision-required (3). --noninteractive always requires --yes. A failing
step aborts the build and reports its phase; an interpreter that cannot be
executed returns 6.
--environment host runs the steps in the current environment. --environment
clean creates a separate build root with a fixed PATH and no HOME, and is
not isolation from the host kernel. --environment vm needs a booted Holy
image and qemu-system; when neither is available it returns 6 and names the
missing capability. Git sources, converter evaluation and cross build roots
are not implemented and are rejected.
CONVERSION
holypkg convert PKGBUILD --source NAME --output NEW_DIRECTORY holypkg con-
vert TEMPLATE --source NAME --output NEW_DIRECTORY holypkg import PKGBUILD
--source NAME --format pkgbuild --output NEW_DIRECTORY holypkg import TEM-
PLATE --source NAME --format void --output NEW_DIRECTORY
A file named template reaches the Void converter and every other name
reaches the pacman converter. Each reads its input as text and writes a
conversion directory holding the original file, a copy of every local
source and directory it names, a holy-recipe-1 manifest and a conversion
report. NAME is a source alias without a colon and is recorded as prove-
nance; it grants no installed source id. The original file is never exe-
cuted.
PKGBUILD
pkgbase, pkgver, pkgrel, pkgdesc, url, license, arch, epoch, depends,
makedepends, checkdepends, optdepends and backup are carried. A list is
read the way a shell reads one: whitespace separates elements, so a comma
belongs to the element that holds it. A comparison operator becomes the
matching relation: >= ge, <= le, > gt, < lt, = eq. arch maps x86_64, x86 to
i686, and noarch or any unchanged; any other list needs review. A source
entry has $pkgbase, $pkgname, $pkgver and $pkgrel expanded, and a
sha256sums entry becomes source-sha256. A local source is copied next to
the recipe and its original PKGBUILD line is recorded. A function body ends
at the first closing brace that is not quoted and not part of a command or
parameter substitution.
prepare, build, check and package keep their bodies in Bash, preceded by a
prologue that rebuilds the makepkg variables from the exported Holy paths:
$pkgdir becomes $HOLY_DEST, $srcdir becomes $HOLY_SRC, $startdir and
$builddir become $HOLY_BUILD, and a $pkgdir inside a split step becomes
$HOLY_SPLIT_DEST. $srcdest, $pkgdest, CFLAGS, CXXFLAGS, CPPFLAGS, LDFLAGS
and MAKEFLAGS are redefined only when the environment leaves them empty. A
single source is lifted out of its own name directory so that HOLY_SRC is
the makepkg $srcdir. A package_NAME function becomes split-step for the
output NAME, and install= fragments are copied into the payload under
/usr/share/holy and recorded as a postinstall hook, so pre_install and
post_install arrive as one hook.
provides, conflicts and replaces, epoch, groups, noextract, validpgpkeys,
options, non-SHA-256 checksums and architecture-specific source lists are
preserved as x- records or reported as unknown.
Void template
pkgname, version, revision, short_desc, homepage, license, maintainer and
changelog are carried. distfiles is read as a list and $pkgname, $pkgbase,
$version, $revision, $sourcepkg and $pkgver are expanded; a checksum entry
becomes source-sha256, a leading @ contents digest is reported as unknown,
and a local distfile is copied next to the recipe. hostmakedepends, makede-
pends and checkdepends become build-depend, and depends becomes depend with
the same comparator names as the PKGBUILD converter; a virtual? requirement
and a build dependency that carries a version are reported as unknown.
conf_files becomes config and mutable_files becomes config mutable, and a
path with a wildcard needs review.
pre_fetch, do_fetch, post_fetch, the extract, patch, configure, build,
check and install families keep their Bash and become the matching Holy
phase, each behind a prologue that rebuilds the xbps-src variables from the
exported paths: $wrksrc and $build_wrksrc become $HOLY_SRC, $masterdir be-
comes $HOLY_WORK, $XBPS_BUILDDIR becomes $HOLY_BUILD, $XBPS_MAKEJOBS be-
comes $HOLY_JOBS, and $DESTDIR becomes $HOLY_DEST with $PKGDESTDIR the
same, or $HOLY_SPLIT_DEST in a split step. The step changes into $wrksrc
first because xbps-src runs a phase there. A distfile is extracted into
HOLY_SRC, which therefore takes the place of the xbps-src $wrksrc direc-
tory. The v* helpers a body calls are carried into the prologue; vman, vsv,
vsed, vcompletion and vsrccopy are reported as unresolved, as is any use of
a cross, verbose or chroot variable.
A NAME_package function becomes output NAME plus a split step carrying its
pkg_install body, so DESTDIR stays the main tree and PKGDESTDIR becomes the
staging tree. depends, replaces, conflicts and short_desc set inside that
function are reported with their lines rather than moved into the main out-
put, because a Holy recipe has one depend list. A patches directory is
archived next to the recipe and applied in the prepare step with a .args
file per patch, and a files directory is archived next to it and named by
FILESDIR. An INSTALL or REMOVE file becomes one hook and is reported, be-
cause a Holy hook runs with ACTION unset and its pre and post branches are
not reproduced.
build_options are fixed to build_options_default before they reach the
recipe, so vopt_if, vopt_with, vopt_enable, vopt_bool and vopt_feature pro-
duce their value and a top level vopt_conflict is checked against that same
set. The remaining build variables, such as bootstrap, repository, archs,
shlib_provides, nostrip and skip_extraction, are preserved as x- records. A
phase the template leaves out is reported as supplied by common/build-
style/NAME.sh, which the converter does not run, so its result is review-
required. A conditional block, a case block or a vopt_conflict is reported
with its line, since the converter evaluates none of them.
Aports APKBUILD
pkgname, pkgver, pkgrel, pkgdesc, url, license and maintainer are carried.
arch maps all and noarch to noarch, x86_64 to x86_64 and x86 to i686; any
other machine name and a negated architecture are reported. depends becomes
depend and makedepends, makedepends_build, makedepends_host and checkde-
pends become build-depend with the same comparator names as the PKGBUILD
converter; a !NAME conflict becomes x-conflicts, a so: or cmd: prefix is
reported, and the per subpackage depends_NAME lists are preserved with
their lines because a Holy recipe has one depend list.
source is read as a list and $pkgname, $pkgver and $pkgrel are expanded,
with $pkgver giving the bare upstream version there and version-rrelease in
a dependency; a filename::url target name is preserved. A local source is
copied next to the recipe and hashed as SHA-256, and a mismatch against a
declared sha256sums entry is reported. A remote source is carried with its
declared sha256, or reported when there is none, because aports pins
sha512sums, which a Holy source cannot use, and the report names every such
source.
prepare, build, check and package keep their shell and become the matching
Holy phase, each behind a prologue that rebuilds the abuild variables from
the exported paths: $srcdir becomes $HOLY_SRC, $startdir becomes
$HOLY_WORK, DESTDIR becomes $HOLY_DEST with PKGDESTDIR the same, or
$HOLY_SPLIT_DEST in a split step, $pkgdir stays the main tree and $subp-
kgdir becomes the staging tree. $JOBS, $MAKEFLAGS, $SAMUFLAGS,
$CMAKE_BUILD_PARALLEL_LEVEL and the other abuild.conf parallel settings are
rebuilt from $HOLY_JOBS. The step changes into $builddir first because
abuild runs a phase there; a declared builddir is rebased on the source
tree and an absent one is the abuild default of $srcdir/$pkgname-$pkgver,
which is reported. The extracted tree therefore takes the place of the
abuild $srcdir directory, and that lift replaces default_unpack.
Each subpackages entry is NAME, NAME:function or NAME:function:arch, and
the split function is the named one or the last dash-separated suffix of
NAME, with -bash-completion, -zsh-completion and -fish-completion mapping
to bashcomp, zshcomp and fishcomp. A split function with a body becomes
output NAME plus a split step carrying it; one without a body is reported
as needing the abuild default_dev, default_doc, default_static, de-
fault_openrc or default_libs helper, which moves files by pattern, so no
output is declared for it and a declared architecture is reported. An in-
stall entry is post-install, pre-install, pre-upgrade, post-upgrade, pre-
deinstall or post-deinstall; the first and the fourth map onto hook-install
and hook-remove, are copied into the payload under usr/share/holy/NAME and
are reported as a semantic change, since a Holy hook runs with ACTION un-
set, and the rest are reported as having no Holy hook stage.
options, provider_priority, replaces_priority, triggers, install_if, pkg-
groups, pkgusers, giturl, pcprefix, sonameprefix, langdir, soname, provides
and replaces are preserved as x- records. A conditional block is reported
with its line, and so is any other top level statement, since abuild
sources the file as a shell script and the converter executes none of it.
The default_* helpers, the abuild phases the template leaves out, the cross
and chroot variables, $CBUILD, $CHOST, $CTARGET and $CARCH are reported as
unresolved or unreproduced.
SlackBuild script
PRGNAM, VERSION, BUILD, TAG and PKGTYPE are carried, whether they are writ-
ten as NAME=value or as NAME=${NAME:-value}; a computed value is reported
instead of guessed. A SlackBuild script is one linear shell program rather
than a set of phase functions, so it becomes a single build step, and the
prologue rebuilds the variables the script reads: $ARCH becomes $HOLY_ARCH,
$CWD and $TMP become $HOLY_SRC, $PKG becomes $HOLY_DEST with $DESTDIR the
same, $OUTPUT becomes $HOLY_OUT, and $SLKCFLAGS, $CFLAGS, $CXXFLAGS, $LD-
FLAGS and $MAKEFLAGS are rebuilt from $HOLY_JOBS.
The body runs from the set -e line to the /sbin/makepkg call, and the cd
$PKG before that call is left out, because the engine packs the payload.
The engine also fetches and unpacks the recorded archive, so a tar line
that reads $CWD is reported and left out, as is the rm -rf $PRGNAM-$VERSION
that precedes it. The machine the script picks with uname -m becomes
$HOLY_ARCH and the LIBDIRSUFFIX it derives per machine becomes empty on
x86_64, both reported. The archive name comes from the tar line with
$PRGNAM, $VERSION and $BUILD expanded; an archive that is not beside the
script is reported, since this format pins an MD5 sum rather than a SHA-256
digest, and a local one is copied next to the recipe and hashed as SHA-256.
Every other file the script reads through $CWD travels beside the recipe
and is declared as a source, so the engine stages it where $CWD points dur-
ing a step.
slack-desc beside the script carries the summary, the homepage, and the re-
quires and conflicts lists, which become depend and x-conflicts; the other
field keys are preserved with their lines. The info file beside the script
carries the upstream DOWNLOAD and its MD5SUM, which is reported as unus-
able. A doinst.sh the script copies into $PKG/install becomes one hook and
is copied into the payload, and the $PKG/install tree itself is reported as
packaging metadata that stays in the payload as ordinary files. The strip
pass, the ownership rewrite, the user and group creation, the loader cache
update and the desktop, mime, icon and font cache helpers are reported as
unresolved.
RPM spec
Name, Version, Release, Summary, License and URL are carried. A Version or
Release written with a macro in it keeps only its literal part, and the
macro is reported, so the value stays what the spec says; a field with no
literal text at all is refused. The distribution suffix on Release is left
out, which is reported. BuildArch noarch becomes noarch and any other value
becomes any, because the payload decides the machine, and ExclusiveArch is
preserved.
The %global and %define records are collected, and a macro this converter
can resolve is expanded everywhere it appears: the identity fields, the
path macros such as _sourcedir, _builddir, _topdir and buildroot, and the
directory macros such as _bindir and _libdir. A macro that resolves to
nothing and is optional is left out, an unresolved one stays in the text
and is reported, and a value that is a pattern rather than a word stays in-
side the body.
BuildRequires becomes build-depend and Requires becomes depend, with the
same comparator names as the PKGBUILD converter, and the list is read one
record per line because an rpm requirement carries its comparison. Provides
becomes x-provides, Conflicts and Obsoletes become x-conflicts, and Recom-
mends, Suggests and Enhances become x-suggests, since none of them is a re-
quirement. A requirement that names a file, a rich dependency or an rpmlib
capability is reported rather than turned into a record.
Source and Patch records name the files the build needs. A local file be-
side the spec is copied next to the recipe and hashed as SHA-256, and one
that is absent is reported, since a spec normally pins no digest at all.
The engine fetches and unpacks the recorded sources, so the extracted tree
takes the place of _sourcedir.
The prep, build, install and check sections become the prepare, build,
package and check phases, each behind a prologue that rebuilds the macros
the body reads from the exported paths and changes into _sourcedir/NAME-
VERSION when the tree was unpacked under that name. %setup, %autosetup and
%autopatch become a cd into that directory followed by a patch pass over
the declared patches, and %make_install, %make_build and the __ prefixed
helpers become the shell line they stand for. Every other rpm section com-
mand is reported and left in the body, so the build fails visibly rather
than losing a step. An rpm conditional, which this converter does not
choose between, is reported with its section.
A %package block becomes an output, and because an rpm subpackage is a file
list rather than a body, its split step copies the paths its own %files
section named out of the main tree. A path that keeps a macro is reported,
a %files option such as -f is reported, and a subpackage with no %files
list gets no payload. The main %files list is not carried, since it only
checks what the install already placed, and that is reported. The %descrip-
tion blocks are not carried.
Debian source package
A debian directory is recognized by its basename, or named with import
--format debian. The version comes from the first record of de-
bian/changelog, which is the distribution version rather than the upstream
one. It is split at the last dash: an all-numeric tail is the Debian revi-
sion and becomes the Holy release, and a version with no such tail is na-
tive, so its release is one. Either outcome is reported, as is an epoch,
which names a packaging revision order and is dropped. A changelog with no
version on its first line is malformed. The source stanza gives the name,
the section gives the summary of the main binary package, and Homepage be-
comes the homepage.
A relationship field is a comma separated list of groups, and a group may
offer alternatives with a pipe. One Holy depend record names one package,
so the first alternative of each group is carried and the rest are re-
ported. Build-Depends and Build-Depends-Indep become build-depend, Depends
and Pre-Depends become depend, with the same comparator names as the PKG-
BUILD converter; a strict comparison becomes lt or gt, and a Debian version
has the same epoch and revision, so the version keeps its upstream part.
Provides becomes x-provides, Breaks, Conflicts and Replaces become x-con-
flicts, and Recommends, Suggests and Enhances become x-suggests. A dpkg
substitution variable such as $ {misc:Depends} is filled in by dpkg and is
reported, and an entry with an architecture qualifier is reported rather
than turned into a record. Only the source stanza and the main binary pack-
age reach the recipe; a requirement of a subpackage is reported, since that
package is built on its own.
Each binary stanza becomes an output. A binary that names a file list beside
the control file is a subpackage, and because a debian subpackage is a file
list rather than a body, its split step copies the paths that list names
out of the main tree, which is reported. A .install line is a source and a
destination, and a line with no destination names the source itself, so a
directory destination is copied whole. A .docs, .manpages and .links list
names a path the same way, and a list that names no path is reported in-
stead of written as a split step that would fill no tree. The binary that
names no file list is the main output and owns the whole tree. A source
with no binary stanza still builds one output, and more than one binary
that names no file list is reported, since the converter cannot tell which
one is the main tree.
debian/rules becomes the build step, behind a prologue that sets
DEB_HOST_MULTIARCH and DEB_BUILD_OPTIONS and changes into the source tree.
dh is a macro framework rather than a script, so every debhelper call,
every debhelper override and dpkg-buildpackage are reported and left in the
body, which means the build fails visibly rather than losing a step. The
maintainer scripts, debian/copyright, debian/watch and debian/source/format
are copied next to the recipe and reported; a lintian-overrides file is re-
ported as well, since it suppresses a report rather than building anything.
Standards-Version is preserved.
Gentoo ebuild
An ebuild is recognized by its .ebuild suffix, or named with import --for-
mat gentoo. It states its identity in its file name, which is PN-PV-
rPR.ebuild, so the name is the text before the last dash a digit follows
and the version is what follows it; a trailing -rN is the revision and be-
comes the Holy release, and a file name with no revision gets release one.
An epoch names a packaging revision order rather than a version, so it is
preserved and dropped. A file name that carries no version is malformed.
EAPI, LICENSE and SLOT are carried, DESCRIPTION is the summary, and HOME-
PAGE is the homepage. KEYWORDS names the machines an ebuild is tested on
rather than the machine that builds it, so arch is any.
The engine fetches and unpacks the recorded sources, so the extracted tree
takes the place of WORKDIR, which is reported. A mirror:// entry names no
single address and is reported, and a remote archive is reported too, be-
cause a Gentoo Manifest pins a BLAKE2B and a SHA-512 rather than the
SHA-256 a Holy source needs. A PATCHES entry, and any other file named be-
side the ebuild, is copied next to the recipe and hashed as SHA-256. An en-
try behind a USE flag is a file the converter cannot reach, and is re-
ported.
A dependency atom is a category, a package and an optional comparison. The
category is dropped and the name is what a record holds, a blocker becomes
x-conflicts, and a version keeps its upstream part with the same comparator
names as the PKGBUILD converter. A ~ atom is a range and a wildcard is not
a version, so both are reported; a slot, a use dependency and a virtual,
which names an interface rather than a package, are reported as well. RDE-
PEND and PDEPEND become depend and DEPEND and BDEPEND become build-depend.
A USE conditional and an any-of group are reported, since a Holy recipe has
no USE flags and cannot choose between the members of a group, and the
atoms inside one are carried anyway so the build still holds them. IUSE,
REQUIRED_USE, RESTRICT and PROPERTIES are reported as package manager set-
tings.
Each standard phase function becomes the matching phase, and a phase the
ebuild leaves out is the one the inherited eclasses supply. A body keeps
its own shell behind a prologue that rebuilds EPREFIX, ED, D, DESTDIR,
WORKDIR, DISTDIR, T, PN, PV, PR, PF, P, PVR, EAPI and changes into the
source tree. An eclass is a helper environment, so every inherited one is
reported, and so is every ebuild.sh helper a body calls, which means the
build fails visibly rather than losing a step. A pkg_preinst, pkg_postinst,
pkg_prerm or pkg_postrm function runs at install time through Portage, and
a Holy hook carries a script rather than a function, so each is reported.
Pacstall pacscript
A pacscript is recognized by its .pacscript suffix, or named with import
--format pacstall. It is bash with a metadata header written as assign-
ments, so pkgname, pkgver, pkgrel, epoch, pkgdesc, url, license, main-
tainer, repology, arch and gives are carried, with pkgdesc as the summary
and url as the homepage. A value written with $pkgname, ${pkgname},
$pkgver, $pkgrel, $gives, $pkgbase or $epoch, or with a variable the same
file states in an assignment of its own, is written out, and one that needs
the shell to choose a substring is a computed identity and returns 2. An
epoch names a packaging revision order rather than a version, so it is pre-
served and dropped. amd64 and x86_64 become x86_64, i386 and i686 become
i686, any and all become any, and a machine Holy does not carry is written
as it stands and reported. A list of machines is reported, because the pay-
load decides the machine the recipe is built for.
A source entry is NAME::URL, ?NAME::URL or a plain URL, and a sha256sums entry
in the same order becomes source-sha256. The engine fetches and unpacks the
recorded sources, so the extracted tree takes the place of srcdir, which is
reported. A file named beside the pacscript is copied next to the recipe
and hashed as SHA-256. A git address is reported, since a Holy source
fetches archives, and so is a remote source with no sha256sums entry and a
plain http address, since a Holy source is fetched over https.
depends, makedepends and checkdepends become depend and build-depend with the
comparator names the PKGBUILD converter uses, and a group of alternatives
written with a pipe is reported with the first of them carried, since a
Holy record cannot choose one. pacdeps become depend as well, and the fact
that they name packages of the same pacstall repository is reported, be-
cause a Holy resolver has to find them in a source that has them. provides,
conflicts, breaks, replaces, enhances, recommends and suggests name no re-
quirement, so they are preserved as x- records, and an optdepends entry be-
comes x-optdepend with its description. A backup entry becomes config, an
r: prefix becomes config mutable and is reported, since a config record
only marks a file. A list written per machine or per distribution under a
suffixed name is reported, and so is every setting that steers a Pacstall
run: priority, external_connection, incompatible, compatible, mask, noex-
tract, custom_fields and ppa. A digest list other than sha256sums is re-
ported, because a Holy source needs SHA-256.
prepare, build, check and package become the matching phase, and a body keeps
its own shell behind a prologue that rebuilds pkgdir, pacdir, srcdir,
startdir, builddir, TARCH, NCPU, pkgname, pkgbase, pkgver, pkgrel, pacname,
gives and epoch from the exported Holy paths and changes into the source
tree. Every helper the Pacstall environment supplies that a body calls,
such as fancy_message, makepkg, parse_options or ask, is reported, and so
is a variable of that environment a body reads: KVER, STAGEDIR, homedir,
DISTRO, DIR, full_version and pacstall_root. A list of names with a pkgbase
is a split pkgbase: every name becomes an output and its package_NAME func-
tion becomes the split step that fills it, and a package function beside
those is reported. pre_install, pre_upgrade, post_install and post_upgrade
become one install hook, and pre_remove and post_remove one remove hook;
each hook is written beside the recipe, declared as a source and installed
into the payload, and a Holy hook runs with ACTION unset, so the pre and
post bodies arrive in the order Pacstall calls them. A conditional block
and an assignment inside one are reported, since the converter evaluates
neither.
Flatpak manifest
A manifest is recognized by its .json, .yml or .yaml suffix, or named with
import --format flatpak. Only the JSON form is read; another form of the
same manifest returns 2 rather than a guess. The package name is the last
dotted component of the application id, and an id that names a machine and
a branch after a slash keeps that reflex in x-app-id while the branch sup-
plies the machine. A version the manifest states is carried and one it does
not state records zero, which is reported. summary, url, runtime, sdk,
command, branch and the machine are carried; arch is the machine the mani-
fest or its branch names, and any means the machine the build runs on.
The sdk is a build requirement and the runtime a runtime requirement, be-
cause the SDK that builds the modules and the runtime the program runs on
are two different Flatpak ids, and the fact is reported. Each id loses its
leading components, since a record names one package. The command is a path
under /app, so it is kept as x-flatpak-command, and the prefix of a module
becomes /usr. A /app path inside a build command becomes $DESTDIR, so a
converted build writes into the payload and nowhere else.
The engine fetches and unpacks the recorded sources, so one top directory
inside an extracted archive is lifted into place, which is what a module
builds in, and the step changes into that tree. An archive with an https
address becomes a source with the sha256 the manifest pins, and one named
beside the manifest is copied next to the recipe and hashed there. A file,
a patch and an inline source travel beside the recipe as well, an inline
body becoming a file of its own. A patch applies with -p1 in a prepare step
before its module builds, a shell source runs inside the step of its mod-
ule, and a sed source, a directory, a git, svn or bzr checkout, strip-com-
ponents, dest-filename and dest are reported.
Each module becomes one step in module order. Its build-options export env,
cflags, cxxflags, ldflags and cppflags and prepend append-path, and its
pre-commands, build-commands, post-install and post-commands keep their
shell. A simple module is exactly its commands. The make, autotools, auto-
gen, cmake and meson templates are replaced by the shell that runs the same
tools, which is reported, since the template that normally runs them does
not travel with the recipe. The cargo template builds without an install
step and is reported, and a buildsystem with no Holy phase is a helper the
report names rather than a guess.
A finish-args permission, a cleanup step, a build extension and a module
cleanup are dropped and counted, since a Flatpak sandbox decision has no
Holy equivalent. A manifest with more than one module is reported, because
a Flatpak build shares one prefix across its modules while each step here
installs into the payload, so a later module cannot read an earlier in-
stall.
Homebrew formula
A formula is recognized by its .rb suffix, or named with import --format
homebrew. It is Ruby, and it is read as text and never evaluated. The
class name becomes the package name, and a class that inherits from a cask
is reported, since a cask installs an application bundle rather than build-
ing a source tree. The description, homepage and license are carried, the
url and its sha256 become one pinned source, the revision becomes the re-
lease, and a formula that states no version records the version its source
url carries before its archive suffix, or zero, which is reported.
A depends_on becomes depend and a :build dependency becomes build-depend,
while a :recommended or :optional dependency is reported, since a build
cannot promise a feature it did not ask for. A resource or a patch block
that pins a url and a digest becomes a fetched source, and a patch applies
with -p1 in a prepare step. A block that prepares its content with a Ruby
expression is a helper the report names.
A system call inside def install or on_linux becomes a build step that runs
the same command in the source root, because a Homebrew build runs there
and a Holy build step starts in the build directory. The Homebrew prefix is
rewritten onto the build root: #{prefix} and HOMEBREW_PREFIX become
$HOLY_DEST, #{libexec} becomes $HOLY_DEST/libexec, #{etc} and #{var} become
$HOLY_DEST/etc and $HOLY_DEST/var, and #{buildpath} becomes $HOLY_BUILD. An
interpolation this reader does not model keeps its text and is reported. An
engine lift puts the extracted source root where a Homebrew build runs,
since the engine extracts an archive under its own name and a distribution
tarball keeps one top directory inside it.
Every other statement of an install body is Ruby, so it is not translated.
The formula is copied beside the recipe as homebrew-install.rb, the build
declares a ruby build requirement, and the report names the file and line
of each such statement, since a shell step that pretended to run Ruby would
be a lie. A head, a mirror, a uses_from_macos framework, an on_macos,
on_arm or on_intel block, a bottle block, a service block, a test block and
a def other than install are reported: a bottle is a prebuilt foreign bi-
nary, a launchd job has no Linux counterpart, a Homebrew test harness does
not exist here and a development checkout is not the pinned source.
Guix package definition
A definition is recognized by its .scm suffix, or named with import --for-
mat guix. It is Scheme, and it is read as text and never evaluated. The
name, version, synopsis, homepage, license and build system are carried, a
definition that states no version records zero, which is reported, and a
definition whose package form states no name takes the name of its define-
public variable, which is reported as a change.
An origin becomes one pinned source. A url-fetch is fetched by its uri, a
uri written as a string-append becomes the concatenation of its literals
and the expression is reported, and the base32 digest a definition writes
is decoded into the hex a Holy source records. An origin that is not a url-
fetch is reported, since a git or a local origin is a source this converter
does not fetch. A definition that states no origin is refused, since a
build has no source to fetch.
Each name in inputs becomes depend and each name in native-inputs becomes
build-depend. A versioned entry contributes only its name, since a version
constraint is not a name a record can hold, and the entry is reported. A
requirement a definition writes as a name with a range the reader does not
walk is reported.
The build system becomes the tools it runs. gnu-build-system becomes au-
toreconf when the source carries configure.ac or configure.in, then config-
ure with the payload prefix, make and make install, since a guix build con-
figures the package prefix and stages the install with DESTDIR. cmake-
build-system and meson-build-system become their three commands, and a
build system this reader does not replace, and the trivial one whose proce-
dure is a Scheme body, are reported. An engine lift puts the extracted
source root where a guix build runs, since the engine extracts an archive
under its own name and a distribution tarball keeps one top directory in-
side it.
A #:configure-flags, #:make-flags or #:install-flags argument whose entries
are literal strings becomes the flags of the phase that takes them; an ar-
gument whose entry is a Scheme expression keeps its text and is reported. A
#:phases argument replaces the build system phases, a #:tests? that turns
the suite off is reported as a change, and a patch-shebang and any expres-
sion the reader does not evaluate, such as a modulo, are reported.
CONVERSION REPORTThe report lists every carried, preserved, helper, unknown and
changed item with
its source file and line range, and its status is native or review-re-
quired. A review-required conversion returns 3 with the recipe written,
since review is a statement about execution, not about whether the text
could be carried. A missing identity, a computed identity or malformed text
returns 2; an unreadable input returns 6.
RESULTS
Each output artifact is written to the output directory. Its manifest is
regenerated from the copied payload. HOLY/meta records format, name, ver-
sion, release, os, arch, libc, x-version-family holy, x-build-target and
installed-size, plus any x- record. HOLY/deps carries declared depend
records plus one soname requirement per DT_NEEDED entry, each attributed to
the file that needs it and written only into the output that carries that
file. HOLY/provides records the package name and the SONAME of every
shared object in the payload. HOLY/hooks records a declared hook only in
the output whose payload holds its script, and the build fails if an output
declares a hook it does not carry. HOLY/origin records the recipe inputs
with their pinned digests and verification built-locally. HOLY/transform
records the build environment and the declared build dependencies. The fin-
ished .holy is installed by a normal transaction; the build itself never
writes to the target root.
EXAMPLES
format holy-recipe-1
name example
version 1.0
release 1
arch x86_64
libc glibc
summary Example package
build-depend "toolchain:x86_64-linux-gnu"
build-depend cmd:make
source src "https://example.org/example-1.0.tar.xz"
source-sha256 src "0000000000000000000000000000000000000000000000000000000000000000"
output example runtime
output example-doc docs
split example-doc usr/share/doc/*
step build /bin/sh <<BUILD
cd "$HOLY_SRC"
make -j"$HOLY_JOBS"
BUILD
step package /bin/sh <<PACKAGE
cd "$HOLY_SRC"
make DESTDIR="$HOLY_DEST" PREFIX=/usr install
PACKAGE
A second output built from its own staging tree:
output example runtime
output example-doc docs
split-step example-doc split /bin/sh <<DOCS
mkdir -p "$HOLY_SPLIT_DEST/usr/share/doc/example"
cp README "$HOLY_SPLIT_DEST/usr/share/doc/example/README"
DOCS
EXIT STATUS
0 the build produced every declared output. 1 an operational failure. 2 an
invalid recipe, argument or conversion. 3 a decision is required. 6 a re-
quired artifact or system capability is unavailable.
SOURCES
PKGBUILD: https://pacman.archlinux.page/PKGBUILD.5.html APKBUILD:
https://wiki.alpinelinux.org/wiki/APKBUILD_Reference abuild phases:
https://gitlab.alpinelinux.org/alpine/abuild/-/raw/master/functions.sh.in
abuild default.conf: https://git-
lab.alpinelinux.org/alpine/abuild/-/raw/master/default.conf RPM spec:
https://rpm.org/docs/latest/manual/spec.html rpm macros: https://rpm-soft-
ware-management.github.io/rpm/manual/macros.html SlackBuild script:
https://slackbuilds.org/howto/ slack-desc format: https://slack-
builds.org/guidelines/ Void Manual: https://raw.githubusercontent.com/void-
linux/void-packages/master/Manual.md v* helpers: https://raw.githubusercon-
tent.com/void-linux/void-packages/master/common/environment/setup/in-
stall.sh build style: https://raw.githubusercontent.com/void-linux/void-
packages/master/common/build-style/gnu-makefile.sh RPM spec:
https://rpm.org/docs/latest/manual/spec.html SlackBuild template:
https://slackbuilds.org/templates/autotools-template.SlackBuild Debian con-
trol fields: https://www.debian.org/doc/debian-policy/ch-controlfields.html
Debian package relationships: https://www.debian.org/doc/debian-policy/ch-
relationships.html Debian packaging concepts: https://www.de-
bian.org/doc/debian-policy/concepts.html debhelper: https://manpages.de-
bian.org/trixie/debhelper/debhelper.1.en.html Gentoo ebuild:
https://wiki.gentoo.org/wiki/Ebuild Package specification:
https://wiki.gentoo.org/wiki/Repository_format eclass: https://wiki.gen-
too.org/wiki/Eclass USE flag dependencies: https://wiki.gen-
too.org/wiki/USE_atoms Pacstall: https://github.com/pacstall/pacstall Pac-
stall manual: https://github.com/pacstall/pacstall/blob/mas-
ter/misc/man/pacstall.8 Pacstall programs: https://github.com/pacstall/pac-
stall-programs Flatpak manifests: https://docs.flatpak.org/en/latest/mani-
fests.html flatpak-builder modules: https://docs.flatpak.org/en/lat-
est/flatpak-builder.html
holy 2026 HOLY-RECIPE(5)