git mirror - github.com/owenewans/holy - branch master
clone: https://src.holypkg.eu/holy/

file man/holypkg.8

.TH HOLYPKG 8 "September 2026" "Holy" "System Administration"
.SH NAME
holypkg \- Holy local package tooling prototype
.SH SYNOPSIS
.B 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]
.br
.B 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]
.br
.B holypkg up SOURCE:PACKAGE [--prepare] [--output NEW_FILE] [--catalog MIRROR] [--choose SHA256] [--arch ARCH] [--libc LIBC] [--accept-arch SHA256] [--accept-privileged SHA256] [--root DIRECTORY] [--yes] [--noninteractive]
.br
.B holypkg apply PLAN --sha256 PLAN_SHA256 [--root DIRECTORY]
.br
.B holypkg import INPUT --source NAME
.B "     --format pacman|rpm|deb|slackware|apk|xbps|appimage|snap|scoop|winget|nix|eopkg|pkgbuild"
.B "     |void|aports|slackbuild|rpmspec|debian|gentoo|pacstall|flatpak|homebrew|guix"
.br
.B "     --output NEW_DIRECTORY [--public-key FILE (apk only)]"
.br
.B holypkg build RECIPE --output NEW_DIRECTORY [--environment host|clean|vm] [--work NEW_DIRECTORY] [--jobs N] [--yes] [--noninteractive] [--keep]
.br
.B holypkg convert PKGBUILD|APKBUILD|NAME.SlackBuild|NAME.spec|debian|NAME.ebuild|NAME.pacscript|NAME.rb|NAME.scm|NAME.json|TEMPLATE --source NAME --output NEW_DIRECTORY
.br
.B holypkg apt index Packages[.gz|.xz|.zst] --sha256 HASH --source NAME --base HTTPS_BASE/ --output NEW_DIRECTORY
.br
.B holypkg apt sync HTTPS_URL --sha256 HASH --source NAME --base HTTPS_BASE/ --output NEW_DIRECTORY [--ca-file FILE]
.br
.B holypkg apt sync-signed HTTPS_BASE/ SUITE COMPONENT ARCH --source NAME --keyring FILE --output NEW_DIRECTORY [--inrelease] [--files] [--ca-file FILE]
.br
.B holypkg apt sync-source ALIAS SUITE COMPONENT ARCH --root DIRECTORY --keyring FILE --output NEW_DIRECTORY [--inrelease] [--files] [--ca-file FILE]
.br
.B holypkg apt bind ALIAS SUITE COMPONENT INDEX_ARCH CATALOG --root DIRECTORY
.br
.B holypkg apt search QUERY --catalog DIRECTORY [--source ALIAS --root DIRECTORY]
.br
.B holypkg apt search QUERY --source ALIAS --suite SUITE --component COMPONENT --index-arch ARCH --root DIRECTORY
.br
.B holypkg apt info PACKAGE --catalog DIRECTORY [--source ALIAS --root DIRECTORY]
.br
.B holypkg apt info PACKAGE --source ALIAS --suite SUITE --component COMPONENT --index-arch ARCH --root DIRECTORY
.br
.B holypkg apt fetch NAME VERSION ARCH --catalog DIRECTORY --output NEW_DIRECTORY [--source ALIAS --root DIRECTORY] [--ca-file FILE] [--import] [--require-file /PATH]
.br
.B holypkg apt fetch NAME VERSION ARCH --source ALIAS --suite SUITE --component COMPONENT --index-arch ARCH --root DIRECTORY --output NEW_DIRECTORY [--ca-file FILE] [--import] [--require-file /PATH]
.br
.B holypkg appimage inspect INPUT
.br
.B holypkg appimage extract INPUT --output NEW_DIRECTORY
.br
.B holypkg snap inspect INPUT
.br
.B holypkg snap extract INPUT --output NEW_DIRECTORY
.br
.B holypkg apk index APKINDEX.tar.gz --source NAME --base URL/ --output NEW_DIRECTORY
.br
.B holypkg apk verify-index FILE --public-key FILE
.br
.B holypkg apk sync SOURCE REPO --root DIRECTORY --output NEW_DIRECTORY [--sha256 HASH | --accept-unsigned HASH] [--ca-file FILE] [--public-key FILE]
.br
.B holypkg apk bind SOURCE REPO CATALOG --root DIRECTORY [--accept-unsigned HASH] [--public-key FILE]
.br
.B holypkg apk search QUERY [--catalog DIRECTORY | --source SOURCE --repo REPO --root DIRECTORY]
.br
.B holypkg apk info PACKAGE [--catalog DIRECTORY | --source SOURCE --repo REPO --root DIRECTORY]
.br
.B holypkg apk providers soname:NAME [--catalog DIRECTORY | --source SOURCE --repo REPO --root DIRECTORY]
.br
.B holypkg apk fetch-provider soname:NAME ARCH --output NEW_DIRECTORY [--catalog DIRECTORY | --source SOURCE --repo REPO --root DIRECTORY] [--sha256 HASH] [--ca-file FILE] [--public-key FILE]
.br
.B holypkg apk fetch NAME VERSION ARCH --output NEW_DIRECTORY [--catalog DIRECTORY | --source SOURCE --repo REPO --root DIRECTORY] [--sha256 HASH] [--ca-file FILE] [--public-key FILE] [--import] [--require-soname SONAME] [--require-file /PATH]
.br
.B holypkg xbps index REPODATA --sha256 HASH --source NAME --base HTTPS_BASE/ --output NEW_DIRECTORY [--public-key FILE]
.br
.B holypkg xbps sync HTTPS_BASE/ ARCH --sha256 HASH --source NAME --output NEW_DIRECTORY [--ca-file FILE] [--public-key FILE]
.br
.B holypkg xbps sync-source ALIAS ARCH --root DIRECTORY --sha256 HASH --output NEW_DIRECTORY [--ca-file FILE] [--public-key FILE]
.br
.B holypkg xbps search|info QUERY --catalog DIRECTORY [--source ALIAS --root DIRECTORY]
.br
.B holypkg xbps search|info QUERY --source ALIAS --index-arch ARCH --root DIRECTORY
.br
.B holypkg xbps providers SONAME --catalog DIRECTORY
.br
.B holypkg xbps providers SONAME --source ALIAS --index-arch ARCH --root DIRECTORY
.br
.B holypkg xbps fetch NAME VERSION ARCH --catalog DIRECTORY --output NEW_DIRECTORY [--ca-file FILE] [--public-key FILE] [--source ALIAS --root DIRECTORY] [--import] [--require-soname SONAME]
.br
.B 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]
.br
.B holypkg repo mirror HTTPS_BASE/ --sha256 INDEX_SHA256 --output NEW_DIRECTORY [--public-key PUBLIC_KEY.pem] [--ca-file FILE]
.br
.B holypkg repo seal DIRECTORY [--key PRIVATE_KEY.pem]
.br
.B holypkg repo verify DIRECTORY --key PUBLIC_KEY.pem
.br
.B holypkg source plan --config FILE --root DIRECTORY
.br
.B holypkg source apply PLAN --sha256 HASH --root DIRECTORY
.br
.B holypkg source list --root DIRECTORY
.br
.B holypkg source show ALIAS --root DIRECTORY
.br
.B holypkg source catalog bind ALIAS MIRROR [--root DIRECTORY]
.br
.B holypkg sync SOURCE [--root DIRECTORY] [--output NEW_DIRECTORY] [--sha256 INDEX_SHA256 | --accept-unsigned INDEX_SHA256] [--commit GIT_COMMIT] [--ca-file FILE]
.br
.B holypkg sync APK_SOURCE [--repo REPO] --output NEW_DIRECTORY [--root DIRECTORY] [--sha256 HASH | --accept-unsigned HASH] [--ca-file FILE] [--public-key FILE]
.br
.B holypkg sync XBPS_SOURCE --arch ARCH --sha256 HASH --output NEW_DIRECTORY [--root DIRECTORY] [--ca-file FILE] [--public-key FILE]
.br
.B holypkg sync APT_SOURCE --suite SUITE --component COMPONENT --index-arch ARCH --keyring FILE --output NEW_DIRECTORY [--root DIRECTORY] [--inrelease] [--files] [--ca-file FILE]
.br
.B holypkg orphan [--root DIRECTORY] [--json]
.br
.B holypkg conflict [--root DIRECTORY] [--json]
.br
.B holypkg index [--root DIRECTORY] [--path PATH | --capability KIND --name NAME] [--json]
.br
.B holypkg run SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC] [--auto-view] [--view PUBLIC=PRIVATE ...] -- COMMAND [ARGS...]
.br
.B holypkg docs --root DIRECTORY --output FILE
.br
.B holypkg config check FILE
.br
.B holypkg info local:FILE
.br
.B holypkg info SOURCE:PACKAGE [--repo REPO] [--arch ARCH] [--suite SUITE --component COMPONENT --index-arch ARCH] [--catalog MIRROR] [--root DIRECTORY]
.br
.B holypkg search QUERY [--source SOURCE] [--repo REPO] [--arch ARCH] [--suite SUITE --component COMPONENT --index-arch ARCH] [--file] [--fuzzy] [--catalog MIRROR] [--root DIRECTORY]
.br
.B holypkg verify local:FILE
.br
.B holypkg manifest local:FILE
.br
.B holypkg requirements local:FILE
.br
.B holypkg requirements local:FILE --json
.br
.B holypkg provides local:FILE
.br
.B holypkg provides local:FILE --json
.br
.B holypkg solve local:ROOT [local:CANDIDATE ...]
.br
.B holypkg solve local:ROOT [local:CANDIDATE ...] --choose REQUIREMENT_ID=SHA256 [--json]
.br
.B holypkg solve local:ROOT [local:CANDIDATE ...] --json
.br
.B holypkg fetch local:FILE --output DIRECTORY
.br
.B holypkg fetch local:FILE --extract --output DIRECTORY
.br
.B holypkg fetch SOURCE:PACKAGE [--catalog MIRROR] --output DIRECTORY [--extract] [--root DIRECTORY]
.br
.B 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]
.br
.B 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]
.br
.B 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] [--require-file /PATH]
.br
.B holypkg fetch https://URL --sha256 SHA256 --output DIRECTORY [--ca-file FILE]
.br
.B holypkg pack DIRECTORY --output FILE.holy
.br
.B holypkg manifest generate DIRECTORY --output FILE
.br
.B holypkg split TREE --output NEW_FILE [--split OUTPUT GLOB ...] [--debug]
.br
.B holypkg elf FILE [--build-id]
.br
.B holypkg check local:FILE --root DIRECTORY
.br
.B holypkg check local:FILE --root DIRECTORY --json
.br
.B holypkg check [SOURCE:PACKAGE] [--root DIRECTORY] [--arch ARCH] [--libc LIBC] [--json]
.br
.B holypkg files SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC]
.br
.B holypkg why SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC] [--json]
.br
.B holypkg owner PATH [--root DIRECTORY]
.br
.B holypkg repair SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC] [--plan PLAN_SHA256]
.br
.B holypkg rm SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC] [--yes] [--accept-broken]
.br
.B holypkg elf FILE
.br
.B holypkg scan local:FILE
.br
.B holypkg preview local:FILE --root DIRECTORY
.br
.B holypkg preview local:FILE --root DIRECTORY --json
.br
.B holypkg cache stage local:FILE --root DIRECTORY
.br
.B holypkg cache verify SHA256 --root DIRECTORY
.br
.B holypkg cache list --root DIRECTORY
.br
.B holypkg cache clean SHA256 --root DIRECTORY [--yes [--accept-unavailable]]
.br
.B holypkg db init --root DIRECTORY
.br
.B holypkg db status --root DIRECTORY
.br
.B holypkg db status --root DIRECTORY --json
.br
.B holypkg db reserve SHA256 --root DIRECTORY
.br
.B holypkg db cancel --root DIRECTORY
.br
.B holypkg db recover --root DIRECTORY
.br
.B holypkg db recover --abort-empty --root DIRECTORY
.br
.B holypkg db recover --continue --root DIRECTORY
.br
.B holypkg db recover --finish-apply --root DIRECTORY
.br
.B holypkg db preflight --root DIRECTORY
.br
.B holypkg db preflight --root DIRECTORY --json
.br
.B holypkg db plan --root DIRECTORY
.br
.B holypkg db approve PLAN_SHA256 --root DIRECTORY
.br
.B holypkg db recheck --root DIRECTORY
.br
.B holypkg db apply --root DIRECTORY
.br
.B 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
.br
.B 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
.br
.B holypkg db recover --finish-set|--continue-set --root DIRECTORY
.br
.B holypkg db configure-plan SHA256 --root DIRECTORY
.br
.B holypkg db configure-apply PLAN_SHA256 SHA256 --root DIRECTORY
.br
.B holypkg db configure-recover SHA256 --retry --root DIRECTORY
.br
.B holypkg db repair-plan SHA256 --root DIRECTORY
.br
.B holypkg db plan-update OLD_SHA256 NEW_SHA256 [--accept-arch NEW_SHA256] [--accept-privileged NEW_SHA256] --root DIRECTORY
.br
.B holypkg db apply-update PLAN_SHA256 OLD_SHA256 NEW_SHA256 [--accept-arch NEW_SHA256] [--accept-privileged NEW_SHA256] --root DIRECTORY
.br
.B holypkg rollback TRANSACTION [--root DIRECTORY] [--apply PLAN_SHA256] [--accept-arch ARTIFACT_SHA256] [--accept-privileged ARTIFACT_SHA256]
.br
.B holypkg db recover --update --root DIRECTORY
.br
.B holypkg db repair SHA256 --plan PLAN_SHA256 --root DIRECTORY
.br
.B holypkg db recover --repair --root DIRECTORY
.br
.B holypkg db check SHA256 --root DIRECTORY
.br
.B holypkg db check --all --root DIRECTORY
.br
.B holypkg db check SHA256|--all --root DIRECTORY --json
.br
.B holypkg db rm SHA256 [--accept-broken] --root DIRECTORY
.br
.B holypkg db owner PATH --root DIRECTORY
.br
.B holypkg repo index DIRECTORY
.br
.B holypkg repo list DIRECTORY
.br
.B holypkg repo requirements DIRECTORY NAME
.br
.B holypkg repo search DIRECTORY NAME
.br
.B holypkg repo search DIRECTORY NAME --fuzzy
.br
.B holypkg repo search-file DIRECTORY ABSOLUTE_PATH
.br
.B holypkg repo search-file DIRECTORY NAME_OR_PATH --fuzzy
.br
.B holypkg repo solve DIRECTORY NAME [--json]
.br
.B holypkg repo solve DIRECTORY NAME --choose REQUIREMENT_ID=SHA256 [--json]
.br
.B holypkg repo providers DIRECTORY KIND NAME
.br
.B holypkg repo providers DIRECTORY KIND NAME --json
.br
.B holypkg repo seal DIRECTORY
.br
.B holypkg repo fetch DIRECTORY SHA256 --output DIRECTORY
.SH DESCRIPTION
.SS 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=unverified. An explicit key
verifies the APK signature but does not grant a registered source identity.
.PP
--format rpm uses librpm to read the header and payload of v4 and v6 packages.
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 libsolv
RPM comparator. Ordinary versioned Requires and Provides use that comparator.
Automatic self config-file and rpmlib requirements remain in origin rather
than becoming runtime dependencies. Conflicts, Obsoletes, scriptlets,
triggers and file capabilities require further conversion support.
Local import does not verify RPM signatures. Builds without librpm return
status 6 for this format.
.PP
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 separate
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 transaction 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 environments.
.PP
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 package 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 after rechecking the repomd, primary,
catalog and capabilities digests. rpm providers CAPABILITY 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 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.
.PP
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 catalog under the
target database. The common fetch RPM_MD_SOURCE:PACKAGE command takes --version
and --arch, checks the bound catalog and the current registry, and downloads
through the same verifier. A missing version or architecture returns
decision-required (3). An explicit --catalog must resolve to the bound catalog
directory. Changing the registered url leaves the earlier binding invalid, so
search and fetch report coverage unavailable until a new sync succeeds. No
repository signature is checked: verification stays hash-pinned, and trust
require needs a key this backend does not implement.
.PP
--format deb reads the Debian ar members debian-binary, control.tar and
data.tar in order, including supported compressed variants. It retains control
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 package versions mark it config.
Comma-separated Depends entries become package requirements. OR alternatives
remain one requirement with version constraints 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 family. Architecture qualifiers, Pre-Depends and
other unsupported relationships remain attributed foreign requirements that
require an explicit decision 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 virtual claim
does not satisfy a versioned dependency. Unsupported or duplicate claims remain
foreign requirements. The converter supports Architecture 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.
.PP
.B 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.
.B apt sync
downloads Packages over HTTPS into private staging, checks its explicit SHA-256
pin and creates the same local catalog. It does not authenticate the Debian
Release/InRelease signature.
.B 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 operation. The command does not register that keyring as a source-id or
implement the full APT repository policy. Signed catalog fetch receipts use
verification=release-gpgv-user-key. Missing Valid-Until is accepted.
.B --inrelease
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.
.B --files
also downloads COMPONENT/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, retrieval time and partial coverage status. A missing signed
Contents entry fails this requested sync.
.B 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 architecture in
apt fetch is distinct from --index-arch.
.PP
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.
.PP
.B apt search
and
.B apt info
read the catalog and recheck its original index digest. Search matches package
name substrings; info requires an exact name and returns decision-required if
several versions or architectures match.
.B apt search /PATH --file
looks up an exact file path in the verified Contents index and prints matching
indexed package names. Without --files coverage 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 imported package payload must still
be checked before a file requirement is satisfied.
.B apt fetch
selects an exact name, version and Debian architecture, 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 output 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 signature hashes. Direct local DEB import remains
unverified. Index and selection
receipts say pinned-unverified when only an external pin is available. The
signed path uses the separately selected keyring and Release.gpg. Registered
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
.holy manifest. A false Contents claim fails without a successful selection
receipt. The option requires --import.
It does not install packages. make check-apt covers local and HTTPS indexes,
tampering, TLS fetch, hash mismatch and import identity.
.PP
--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.
install/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.
.PP
--format apk reads two or three concatenated gzip members from an APK v2
package. It retains .PKGINFO, optional signatures and control scripts beneath
HOLY/foreign/apk. When .PKGINFO contains datahash, the importer checks its
SHA-256 against the compressed data member before creating outputs. With
--public-key FILE, the importer snapshots an RSA PEM key, requires 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 public-key SHA-256. The caller
must establish the key's authenticity outside the import command. Unversioned
package, so:SONAME and cmd:COMMAND
dependencies 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 versioned
requirements and other unsupported tokens retain their original expressions
as foreign requirements. APK virtual
provides, install-if and replaces also require review because their selection
and file-ownership semantics differ from Holy's simple capabilities. Scripts remain
review-required 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.
.PP
--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 import. Package revision becomes the
.holy release. Exact
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 requirements. Provides are preserved in
origin metadata without creating dependencies. INSTALL and REMOVE scripts
remain review-required. Unknown file-list fields, unsupported architecture,
and ELF/architecture contradictions 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.
.PP
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,
architecture, archive SHA-256 and size. Search and info read the catalog after
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 registry. 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 retain 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 binding. 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 authority for a new key.
.PP
The common sync XBPS_SOURCE command takes --arch and an explicit repodata
--sha256. It verifies the registered source and key, then binds the catalog.
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 resolve to the bound
catalog directory; a copied unbound catalog is rejected.
.PP
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 requires
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 import remains
unverified and has no asserted repository generation.
--require-soname requires --import and checks exact SONAME claims
from the converted ELF payload; an index hint alone does not satisfy it.
The complete fetch receipt records source-id for a checked source binding,
whether import 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 index parsing, HTTPS sync/fetch, registered source
checks, RSA signature verification, conversion and failure cases on a local
fixture server. Key enrollment and automatic provider discovery remain open.
.PP
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 unverified 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 compressed-control checksum, then
compares .PKGINFO identity and optional datahash with the compressed data
member. --sha256 adds a full artifact digest supplied by the caller. The output
contains the original APK and a selection receipt with index/catalog hashes.
For a bound source with a configured RSA key, apk fetch requires --public-key,
checks its fingerprint against the registered key, and verifies the APK
signature over the compressed 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 APKINDEX.tar.gz over HTTPS. A configured RSA
public-key freezes the key fingerprint 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 definition, 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 target 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.
.PP
The common sync APK_SOURCE command calls the same APK verifier and binds its
result. A single configured repository is selected when --repo is absent;
multiple repositories require --repo and return decision-required (3). APK
sync requires --output. --commit applies only to holy-git sources.
.PP
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 imports it, then checks the exact ELF SONAME. Multiple
candidates return decision-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 signature. --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 converted manifest. Both requirements need --import and can be
combined. Import failure leaves the original and an incomplete output
without a complete selection receipt. The APK binding is separate from the native catalog 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
.PKGINFO datahash and checks it against the compressed data member. A bound
keyed source also requires the package signature and matching public-key filename.
.PP
The importer reads .PKGINFO identities, full epoch/version/release strings,
package relations and original field values. It preserves .PKGINFO, .BUILDINFO,
\&.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.
.PP
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, unclassified 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.
.PP
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 become
foreign requirements; the current solver reports decision-required for a
selected output that carries them.
Optional and build requirements remain in attributed origin records.
An .INSTALL script remains review-required. The current installer refuses its
nonempty hooks record. Split outputs also require scoped dependency and
resolved requirements before installation. 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.
.PP
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-specific converter identity;
executable build attestation and publisher verification remain unimplemented.
.PP
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 decoder. The static dependency profile builds all five codecs with musl and rejects a
libarchive configuration missing any of them. Import uses anonymous spool files instead of extracting
foreign paths on the host. It rejects 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 publication.
.PP
make check-pacman runs parser and archive fixtures, including a retained
\&.PKGINFO from Arch gzip 1.15-1, malformed archives, hooks that must not execute,
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 without 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.
.PP
make check-pacman also runs 92 attributed version comparisons from pacman 7.1.0,
plus scoped candidate selection, mixed-family rejection, installation and
rejection of updates that violate an installed consumer's constraint.
.PP
The reference metadata format is
https://alpm.archlinux.page/specifications/PKGINFO.5.html.
.SS Pinned HTTPS catalog mirrors
repo mirror downloads one explicit catalog generation into a new local
directory. 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.
.PP
The mirror verifies every archive, payload, known ABI, dependency record,
identity, size and indexed capability claim before sealing its local current
pointer. The final sealed index must still match INDEX_SHA256. A truncated,
duplicate, forged or unavailable object cannot publish a usable catalog.
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.
.PP
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
directories are refused. Download or validation errors leave partial files
without 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.
.PP
The caller supplies the digest. Without --public-key, this command does not
verify a publisher signature. With --public-key, it verifies the signed index
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.
.SS Installed documentation
holypkg docs --root DIRECTORY --output FILE creates a new holy-docs-1 bundle
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.
.PP
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 separate attributed pages after inode-group checks.
The compressed source digest identifies the original file. Symlink aliases
are checked without following them and listed as references rather than expanded.
.PP
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.
.PP
The command builds a temporary file beside FILE and publishes without replacing
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.
.SS 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
publishing 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 targets;
fetching alone does not install a package.
.SS Bootstrap shell package
The repository target make bootstrap-busybox ARCH=i686|x86_64 INPUTS=DIRECTORY OUTPUT=DIRECTORY
builds a small BusyBox package with static musl linkage. The default is x86_64;
i686 requires multilib GCC and records package architecture x86. INPUTS must contain
musl-1.2.5.tar.gz and busybox-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 linkage when that flag is available; the musl link otherwise uses
only the selected static libraries.
.PP
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 accompany 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 disabled.
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 local bootstrap
experiment, not an official base image or a general recipe engine.
.PP
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 packages
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 chroot. Boot,
static holypkg and full libc recovery require separate acceptance tests.
.SS 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.
.PP
.SS 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 default 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 contains
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.
.PP
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
handling, coldplug, dinit integration and client libudev compatibility remain
separate acceptance cases.
.PP
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++ runtime. 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. Capabilities 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.
.PP
make check-bootstrap-dinit DINIT_PACKAGE=FILE BUSYBOX_PACKAGE=FILE stages both
packages and installs them through approved database plans into a disposable
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.
.PP
make static-deps ARCH=x86_64|i686 INPUTS=DIRECTORY OUTPUT=DIRECTORY KERNEL_HEADERS=DIRECTORY
builds musl dependencies for the selected target from the pinned archives listed with URLs and
SHA-256 digests in profiles/static-sources. It copies and verifies inputs
before unpacking. The caller supplies Linux userspace headers with linux,
asm and asm-generic subdirectories. 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 static 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.
.PP
The output is a new private prefix containing static libarchive/LZ4, zlib, XZ,
Zstandard, bzip2,
libelf, libsolv, curl and OpenSSL, plus build compatibility 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 status and elapsed seconds
remain in the prefix. Archive decoding includes gzip, LZ4, XZ, Zstandard and
bzip2; the native writer still emits only LZ4 frames.
.PP
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 acceptance gates.
.PP
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.
.PP
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.
.PP
make check-static-network STATIC_HOLYPKG=FILE BUSYBOX_PACKAGE=FILE REPORT=FILE
tests that static client in a libc-free chroot with private network namespace,
local DNS and HTTPS fixtures, and a generated test CA. It verifies real DNS
queries, certificate rejection, digest rejection, downloading and native archive
verification. The client drops to the caller's UID/GID. The fixture creates no
external route and supplies no proxy credentials. Python, OpenSSL, unshare,
chroot and noninteractive doas are host test tools. The packaged static BusyBox
ip applet raises the fixture loopback from inside the libc-free chroot. Physical
network recovery remains a separate test.
.PP
The holy-static-network-test-1 JSON report records architecture, host kernel,
input and fixture hashes, CA hash, per-command argv/status/time/logs, observed
DNS query types, completed coverage and unexecuted cases. A dynamic binary
fails the libc-free probe. REPORT defaults to out/static-network.json. No VM
image is involved, so its report field is null. Temporary keys and rootfs
are removed after the test.
.SS 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.
.PP
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 embedded user/password.
The downloader checks each redirect target before requesting 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 signature,
install files or run hooks. General foreign binary fetch remains separate.
.PP
Pack reads a prepared tree containing only HOLY/ and DATA/. HOLY/ must contain
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 separate work.
.PP
Manifest generate reads DATA/ below DIRECTORY and writes HOLY/files records
to a new output path outside the input tree. It emits directories and ordinary
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 traversal visits an alias first. The group identifier is the SHA-256 of
the anchor'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 packing.
Archive pack and inspection use the active LC_CTYPE locale for libarchive
pathname conversion. Use an available UTF-8 locale for UTF-8 filenames.
.PP
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 requires
/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 absolute symlinks.
.PP
The db plan-set command resolves one explicit root against already cached
candidate hashes and cached artifacts from the installed catalog. It supports exact-name package dependencies and the documented pacman version constraints between Linux
data, native static and restricted dynamic packages, and nonempty hooks only after an explicit skip decision,
existing or declared safe directories and the same payload subset as db apply. It reports 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, explicit provider choice
and every selected manifest. Inter-package path and slot conflicts are rejected.
.PP
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 explicit 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.
.PP
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 consume this decision.
.PP
db configure-plan reviews native postinstall records with the syntax
.B 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.
Foreign-script records and other phases remain
unsupported by this command. db configure-apply requires that hash and root
privileges; it runs each reviewed 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 arbitrary root code.
.PP
Before each hook, configure-apply synchronizes a running record in
transactions/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 operations.
The current command handles native postinstall hooks after installation;
preinstall, editing, service consent and automatic configure during add remain open.
.PP
Repeated --accept-privileged SHA256 confirms installation of regular executable
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 remain 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.
.PP
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 artifact, 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 metadata, 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.
.PP
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 decision; check
reports accepted-arch-mismatch in text or an optional architecture 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. Update-specific architecture
approval and automatic execution-capability probing remain unimplemented.
.PP
Repeated --source ARTIFACT=SOURCE_ID explicitly associates selected new artifacts
with active registered sources. It is a user-confirmed origin association for
local delivery, not proof of retrieval or signature verification. 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 requires a decision. Missing or inactive
registered sources return 6.
.PP
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 original source and alias; specifying a
new association for a reused instance returns 3. Source migration requires a
separate operation, which is not implemented yet.
.PP
An already installed dependency is reused only after its metadata matches the
cached archive, its payload and ownership pass checks, and its own selected
dependency edges remain unchanged. Its state, reason and graph remain 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 decision-required; this interface does
not yet promote dependency reasons or perform reinstalls.
.PP
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 compares 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 format. Multiple viable providers still
require --choose; automatic installed/source preference ranking remains open.
.PP
Dynamic sets require literal absolute PT_INTERP paths. Absolute DT_NEEDED paths
must name exact provider payload paths. For a bare SONAME, the consumer 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/SONAME, or own its symlink chain. The planner checks architecture,
runtime, symbols and version needs, and rejects unknown tokens or empty path entries.
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.
.PP
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
publication and removing the journal. --continue-set additionally installs
packages that have no installed instance yet, after checking the whole remaining
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.
.PP
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 retaining
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, accept-broken decision and a
committed marker. The removed instance record is retained there as
.B old-instance
before the database generation changes. Recovery uses that record if removal
stopped after the instance left the installed directory, and retains the
decision when it completes.
Grouped removal remains pending.
.PP
check without a reference inspects all installed instances. check SOURCE:PACKAGE
finds the installed slot using its recorded source ID, including 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 reference 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 artifact. --accept-broken separately
permits removal despite dependent installed packages. The database engine
rechecks files, dependency edges and journal state under its writer lock.
.PP
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.
.PP
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.
.B --associate-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.
.B --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 reviewed plan records each binding and checks it on apply.
.B --accept-arch SHA256
confirms placement of one selected artifact whose architecture differs from
the native target. It does not change the target metadata or prove execution.
.B --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 approval for a previous version is not inherited.
.PP
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 decisions 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.
.PP
add SOURCE:PACKAGE reads one sealed native mirror made by sync. --catalog
selects a mirror for this invocation; without it the manager reads the mirror
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 selected candidate against the index before
staging. Older indexes still use the full candidate pool. If multiple
packages share the root name, it returns 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 artifact 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 public key requires a signed mirror and rechecks its signature.
An unrelated corrupt archive in a current indexed mirror does not block add;
source catalog bind and sync still verify entire mirrors. The current add path
can combine explicitly named candidate packages from other active sources with
.B --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, index 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.
.B --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 resolver checks. The option
requires a native index with complete dependency, file and SONAME records;
an unavailable index returns status 6 and a complete index without a match
returns status 4. Multiple matching packages remain
candidates for the reviewed provider choice.
.B --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 foreign 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. Further 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 unavailable 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 offered.
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 artifact 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 retain their source IDs, and candidate
catalogs are checked again before apply. 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 installation.
.PP
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, rejects duplicate
consumer/id records, and checks that the named source actually 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.
.PP
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 version; 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 release with the source _revision convention before comparison.
An unparsable 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 decisions. Without a terminal or with
--noninteractive, missing approval returns 3 and leaves the plan file for review.
--output selects its path; otherwise up creates a private temporary plan path.
Successful direct application removes that temporary file. --prepare only
writes the plan and retains its temporary path; --yes is invalid with it.
.PP
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 digest, 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 engine; 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.
.PP
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 symlink
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 active 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.
.PP
The db approve command recomputes the plan under an exclusive database lock.
If its SHA-256 matches PLAN_SHA256, the command replaces the prepared
reservation with an approved record bound to its artifact and generation.
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 apply time.
.PP
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.
.PP
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.
.PP
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 accepted. 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
synchronizes 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 installed 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.
Declared directories require local numeric ownership, owner search permission,
and no group/other write or privileged mode bits. Apply prepares an empty
.holy-dir-SHA256 sibling with the requested metadata, synchronizes it, then
publishes it with renameat2(RENAME_NOREPLACE) and synchronizes the parent.
Missing renameat2 support prevents publication. Existing directories are never
chmodded or replaced. Archive entry order does not determine creation order.
A complete staged directory can be reused by set/update/repair recovery;
a partly initialized or nonempty staging directory requires inspection.
Removal retains directories and unregistered contents.
.PP
An I/O failure after journal publication leaves an incomplete transaction
for inspection; db status returns 5 and db recover does not retry file
mutations. 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 recover --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 extraction.
A regular successful return requires both the installed record and
generation publication. General ownership replacement and automatic rollback
are not implemented. The command is not an installer for
arbitrary .holy packages.
.PP
db plan-update OLD_SHA256 NEW_SHA256 --root DIRECTORY previews one explicit
cached replacement. It keeps the installed source-id, name, OS, architecture
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; recovery 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 decision only authorizes
checking and replacing the old file; it does not approve the new hash. Apply
must repeat the same flag. Update journal versions 2, 3 and 4 bind privilege,
architecture and both decisions, respectively, 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.
.PP
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 dependency graph
therefore prevents a valid plan. New dependencies must already be satisfiable
by that proposed set; ambiguous edges return decision-required. Installed reasons
and all current state hashes remain bound to the plan.
.PP
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 sections.
It includes database generation, root/database device and inode, old/new hashes,
source registry digest and current source alias, every installed state hash,
the canonical file delta and the proposed graph. Repeating a preview with the
same inputs produces the same bytes. Alias changes keep source identity but
invalidate the plan; a deactivated registered source returns unavailable (6).
Unassociated local packages retain source-id "-".
.PP
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 artifact 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 implemented.
.PP
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.
.PP
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, generation, artifact
or installed state requires a new preview.
.PP
Before changing target files, apply checks reserved sibling names, publishes
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 rollback.
.PP
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 dependency graph.
The rollback-plan line gives the SHA-256 of the following update 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 committed update whose new
artifact still occupies the installed slot. Missing cache objects return 6.
This operation restores package files and dependencies. It does not reverse
external hook effects, migrations or user data.
.PP
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 manifest hash.
.PP
Hardlink updates stage one inode per new group and link each changed member'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.
.PP
db recover --update revalidates the immutable plan, cached inputs and old/new
file states before continuing the interrupted operation. It recognizes 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. Missing
cached inputs must be restored. Interruption before publishing the immutable
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 interrupted setuid update can leave a reviewed executable under a reserved
.holy-update name until recovery or manual inspection. The reserved name and
mode are shown in the file plan; root operators must treat it as installed code.
.PP
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.
.PP
Static executable installation verifies ELF architecture and nolibc facts;
it does not execute package programs. The caller may run an installed program
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,
malformed 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 returns a requirement error.
Foreign architecture, including x86 on x86_64, returns requirement error 6
until architecture override decisions are implemented. No metadata is relabeled.
.PP
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
readlink 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 instance 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 broken-provider
findings with the absolute target path. For an intact executable script it
checks the shebang interpreter within the target root. A missing interpreter
fails; a malformed shebang, env command, invalid interpreter ELF or unresolved
path reports unknown. This does not test interpreter 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 installed 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. missing-file identifies
an absent file, link or directory, including a path below a missing parent;
changed-config identifies a modified regular file marked config. changed-file
covers other manifest mismatches, including inaccessible or symlinked parents. Paths encode
non-ASCII bytes as \eu00HH; 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 selected literal-path provider files and the supported direct SONAME
case. The coverage label is retained for the current JSON schema.
.PP
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 regular 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 different groups within that manifest.
.PP
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 reviewed policy. The plan binds
the artifact, stored graph, database generation 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 restored; existing directory metadata must remain intact. Repair does not run hooks or change providers.
.B holypkg repair SOURCE:PACKAGE
resolves an installed source slot and prints the same read-only plan. Passing
--plan PLAN_SHA256 applies that exact plan. An inactive source alias remains
resolvable through its recorded source ID. When multiple arch/libc slots match,
--arch and --libc select the intended slot.
.PP
Repair records stage repairing before writing files, verifies restored contents
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 overwritten.
Ordinary check remains read-only. This is not general upgrade, rollback or
configuration-file repair.
.PP
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 requires 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.
.PP
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-recovery 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 executes 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 conversion.
.PP
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 separate 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 closure. The output glibc.holy contains libc.so.6,
its loader and upstream licenses. 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 retained build's upstream test suite as an ordinary user and returns
its actual status. GLIBC_PACKAGE can supply this artifact to
check-libc-recovery; without it that fixture snapshots the host glibc pair.
.PP
make check-libc-abi takes the same inputs as check-musl-abi plus
GLIBC32_PACKAGE=FILE and GLIBC_PACKAGE=FILE. It installs four runtime packages
and four architecture/libc slots of one probe package. It runs pthread, clock
and pipe probes for every ordered pair, removes each runtime, each libc family
and all four runtimes, then checks and repairs through the static client inside
the target root. JSON records runtime, application, 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.
.PP
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 symlink, 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.
.PP
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 returns
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 installed 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 installed
records require manual inspection. --abort-empty applies only to an installing
journal, never to removal. This command does not remove dependents.
.PP
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 extracting 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
duplicate members.
It does not validate the payload manifest, hashes, signatures or installability.
.PP
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 repodata
for --arch; an explicit XBPS query without it returns decision-required (3).
APT sources query the bound Packages index for --suite, --component and
--index-arch; an explicit APT query without them returns decision-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 unopened; source binding still audits the
complete native catalog. An unsigned 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 complete 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 because 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 suggestions for package names. With --file --fuzzy,
QUERY may be a basename or absolute path; only file basenames from a complete
index contribute suggestions. 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 SONAME.
File hints from a legacy index report unknown coverage and exit 6.
.PP
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.
.PP
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 inspection.
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.
.PP
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:
.nf
require ID CONSUMER KIND NAME ARCH LIBC RELATION VERSION ORIGINAL EVIDENCE
.fi
KIND is package, package-or, file, command, soname, symbol-version or build.
For package-or, NAME contains Debian branches as NAME@RELATION@VERSION joined
by |; the outer ARCH, LIBC and RELATION fields are any and VERSION 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. Requirement IDs must be unique. The
command rejects unsupported records and duplicate IDs rather than dropping
them; it does not evaluate version constraints or choose a provider.
With --json, stdout contains holy-requirements-1 requirement events and a
summary, or one error event on invalid input. Each requirement event has
id, consumer, kind, name, arch, libc, relation, version, original and evidence
fields. The JSON encoder maps non-ASCII bytes to \eu00HH escapes; consumers
reconstruct original bytes rather than treating them as normalized text.
.PP
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. Selection 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 appended a
second time. A missing pkgrel on one side does not constrain pkgrel. Numeric
epochs, prerelease letters and separator widths follow pacman's rules.
The Debian comparator uses epoch, upstream version and final Debian revision,
with tilde before the end of a part. Its validator requires 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 before 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 installed 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 executable in the
same artifact; direct hardlinks retain the target's executable mode. A
command claim alone or a nonexecutable file does not satisfy 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 unversioned 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.
Unsupported metadata dependency forms return decision-required when their
consumer 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 requirements
retain exact capability-name matching across families. Automatic newest-version
selection in solve and cross-family overrides remain pending. There are no source IDs, installed packages or
architecture overrides in this command.
.PP
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 references. Hidden/internal symbols cannot provide external references;
explicit versions may use compatibility exports, while unversioned references
require default exports.
.PP
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 reports unknown-symbol-scope
with code 3. An interpreter candidate currently needs a directly recorded ELF
at its literal path with an executable permission bit. Unresolved interpreter paths report
unknown-interpreter-context with code 3, since symlink and merged-/usr package
views are not modeled by this archive-only solver. DT_NEEDED containing a slash
requires a launch context and returns 6. Nested requirements of selected
library packages participate in the same libsolv graph.
.PP
ELF requirement IDs hash package name, arch/libc, consumer path, requirement kind
and target. They remain stable when candidate order changes. Capabilities 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.
.PP
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
unsatisfied requirement, and 6 for unsupported or invalid input. No plan,
install 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 decision.
Script interpreter and plugin requirements are
not inferred from payload in this subset, so selection is not an
installability verdict. Library 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 digests and a count summary after a unique solution. Errors emit
one structured 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 multiple candidate providers, this diagnosis stops at
that edge rather than blaming an unselected candidate.
.PP
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 constraints.
It does not import remote sources or install packages.
The --choose form applies the same one-operation, direct-root requirement
selection after validating the sealed index and matching the selected digest
to an artifact in that generation. It does not write a saved rule.
On success, 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 local
index bytes, not an authenticated publisher or a future installed plan.
.PP
The provides command stages and verifies a native archive, then reads
HOLY/provides as typed claims. Each supported line has seven tokens:
.nf
provide KIND NAME ARCH LIBC VERSION EVIDENCE
.fi
KIND accepts package, file, command, soname, symbol-version or build. ARCH
accepts any, x86, x86_64 or noarch; LIBC accepts any, glibc, musl or nolibc.
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. Repository 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 \eu00HH; callers must reconstruct the original bytes.
.PP
The local fetch command copies any regular input file to an existing directory
as SHA256.holy. It copies bytes without interpreting metadata, extracting files
or running hooks. It creates a mode 0600 temporary file relative to the opened
output directory and synchronizes it
before a no-overwrite hardlink creates the object. Repeating the command checks
the existing object's digest. A mismatched object is left untouched. Fetch
does not establish archive validity, provenance or a trusted root-owned cache.
Configured source fetching remains separate from the pinned HTTPS mirror command.
.PP
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 DIRECTORY is created, partial output remains for inspection.
This command is not a system installation or a root transaction.
.PP
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 directory components
through directory descriptors without following symlinks. 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.
.PP
For matching regular ELF payloads, check reads PT_INTERP and tests whether an
executable ELF file with matching class and machine exists at that absolute
path inside the target root. Missing targets report missing-interpreter;
the wrong class or machine reports incompatible-interpreter. The lookup follows
ordinary symlinks using openat2 with RESOLVE_IN_ROOT and RESOLVE_NO_MAGICLINKS.
Absolute symlink targets and parent traversal stay within the opened target root;
merged-/usr and final loader links are supported. 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=openat2: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 shebang line and
looks up its absolute interpreter inside the target root. A missing target
reports missing-interpreter. An existing executable ELF satisfies 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 libraries are not recursively checked.
If all findings are unknown, the summary status is unknown rather than fail;
the check still returns nonzero because it cannot report pass.
.PP
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 invalid-package or check-error
and status unknown. Paths use byte escapes. Only check, requirements, provides
repo providers, solve, preview and database preflight support --json in this prototype.
.PP
The developer-facing elf command reads a regular ELF through libelf/GElf
without executing it or invoking ldd. It prints ELF class, numeric e_machine
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 interpreters. 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 symbol's binding, type, visibility, section index, version index,
hidden-version flag, version name and version-provider SONAME when recorded.
Undefined 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 properties 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 unknown. It does not model symbol lookup order or plugin loading;
its output is not a dependency resolution or package ABI verdict.
.PP
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.
.PP
The local scan command stages a regular .holy input in a private file, verifies
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 interpreter that disagrees
with the libc tag. For ET_DYN without an interpreter, 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. Architecture-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 requirements or resolve dependencies.
For each ELF it also prints SONAME, DT_NEEDED, GNU version needs with weak or
required status, version definitions and dynamic symbols with the fields
described above, when present. These file-scoped
facts do not prove that a provider exists or that a versioned symbol resolves.
.PP
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
unresolved script-interpreter edges. It does not parse env indirection or
verify that an interpreter exists. For each #! payload it reports the
interpreter 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 unknown;
no helper's existence is inferred. Any of these counts requires resolution.
Preview returns decision-required for nonempty HOLY/hooks and refuses privileged
payload modes. A zero-conflict preview is not an
installable 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.
.PP
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 publication. 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.
.PP
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 digest,
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.
.B 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
requires an intact database and does not verify payload bytes.
.B 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 preview 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 object clears that marker.
An incomplete transaction blocks deletion. Unknown cache entries, unsafe file
types and transaction records too large to inspect cause an error instead of
deletion. The command does not remove installed files or rewrite transaction
history.
.PP
Database init creates an empty ROOT/var/lib/holypkg with installed,
transactions 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 installed
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.
.PP
Database reserve checks a verified SHA-256 object in the target-root cache,
locks the database at its current generation and publishes one
fsynced prepared 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 malformed journal entry remains an error;
cancel will not discard it silently.
.PP
Status --json emits one holy-db-status-1 state event with numeric generation
and pending null or a prepared/approved SHA-256 object. An approved object
also includes its plan digest. A malformed database emits one
invalid-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.
.PP
Database recover handles interruption of reservation publication. It
accepts 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. Recover
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 installed payload. For an incomplete removing transaction,
--continue verifies and removes any remaining listed files. It does not replay
hooks or undo installed files.
.PP
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 local:FILE.
It returns 0 for a zero-decision preview, 3 for unresolved requirements 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 reservation into an approved plan. File changes outside
the database lock can invalidate 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 reservation event with
generation and artifact. A hook decision emits a decision-required error event;
database preflight also emits its reservation event. Invalid input and unavailable
reservations emit a structured error event without partial path output.
.PP
The repository index command validates regular .holy files in DIRECTORY and
writes an unsigned, lexicographically ordered index through an atomic rename.
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 comparator family. The last field is - when the artifact declares no
comparator. 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 digest and quoted nondirectory path from the verified manifest;
readers compare them to the artifact. A separate coverage line declares
complete native 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 library 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 artifact 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 repository format.
.B 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; package installation still verifies the
downloaded artifact and scans its ELF files.
.PP
The seal command validates the entire local index, creates an index.SHA256
generation and atomically publishes a current pointer containing its digest.
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 unavailable
to list and search rather than exposing an unsealed draft.
Without --key, the pointer and artifacts remain unsigned. With --key, seal
requires an Ed25519 PEM private key, signs the exact index generation and
publishes its 64-byte signature.SHA256 file before changing current. Existing
signatures for the same generation must verify under the supplied key; seal
does not replace them. Existing generation files remain in place.
.PP
repo verify requires an Ed25519 PEM public key. It checks the selected index
digest, the signature over the exact index bytes and the referenced package
artifacts. A missing or changed signature, wrong key or altered index returns
4. The private key stays on the publishing machine. With --public-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 enforces a public key frozen
by source plan when one is registered. Verification 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
channel and retain separate generation or hash pins where freshness matters.
.PP
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 packages
or activates this directory as a package source.
.PP
The repository search command verifies the same complete local index and
returns exact name matches. repo search-file takes an absolute path and returns
matching package records plus coverage status, index hash and timestamp.
With a version-3-or-newer index, it verifies only indexed candidates; a changed
unselected archive does not invalidate this query.
Legacy generations return unavailable coverage and status 6. No match in a
complete 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.
.PP
Repository providers searches the sealed local catalog for an exact KIND and
NAME match. It finds indexed claims from HOLY/provides and the package's own
metadata name for KIND=package. With a version-5 index, KIND=soname 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 unselected object can change without
invalidating this query; no match describes the sealed index generation, not
every file in the repository directory. 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.
.PP
The make check-solver target (also run by make check) exercises an internal libsolv interface
with normalized, exact capability IDs and AND/OR, conflict 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 reporting a passed fixture.
The internal unique-solution check returns decision-required when excluding a
selected provider permits a second solution. It does not rank sources or ask
the user for a choice. This detects differing 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 error 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.
.PP
The repository fetch command requires a lowercase SHA-256 from the sealed
index. It verifies the complete index and artifacts, then copies the selected
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 unsigned.
.SH 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.
.PP
The plan binds the previous registry digest, installed database generation and
database device/inode. Apply stages the plan, verifies its approved digest and
checks those inputs under the database writer lock. It never rereads config or
includes. A changed input, another database or a discarded historical source
returns decision-required (3). Malformed proposed registry data returns 2;
I/O or existing-state corruption returns 1; an incomplete transaction returns 5.
.PP
The registry is /var/lib/holypkg/sources under the target root. Its
holy-sources-3 records contain ID, alias, active/inactive state, canonical
definition, trust policy, public-key fingerprint, parent ID, family and priority.
source show ALIAS prints the effective registered definition and policy for an
active alias without downloading its catalog. Key rotation
retains the source ID and changes the registry revision. Replacement uses a
private temporary file, fsync, rename and directory
fsync. Registry revisions are separate from installed database generations.
List prints identities and status without endpoint definitions.
.PP
source catalog bind ALIAS MIRROR verifies the entire sealed mirror, its index,
objects, source-id and exact registered URL. A source with a registered 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 installed 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.
.PP
An ID identifies a configured origin; it is not signature evidence. The current
registry retains inactive origins and supports explicit local-artifact 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 validator as repo mirror. A
registered public key makes the signature mandatory and records its hash with
the source ID. Without
--output, sync publishes a verified generation under
/var/cache/holypkg/catalogs/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 directory URL ending in slash. It writes the registered
source-id in mirror-origin 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.
.PP
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 catalog cache and binding
apply. The Git executable is a sync-time tool, not a package query dependency.
Git transport credentials must be supplied outside the source definition;
the source URL and provenance record do not accept embedded credentials.
.PP
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.INDEX_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.
.PP
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 selected package object are verified before
the named package is copied into an existing --output directory. --extract
instead writes a new output directory. More than one package with the same name
requires a choice (status 3); an absent package or inactive source returns 6.
The mirror record retains the digest pin or verified key hash. A bound signed
catalog is rechecked against the registered key before source queries.
.PP
For an active type apk source, fetch SOURCE:PACKAGE uses a bound APK catalog.
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
.holy outputs. --require-soname and --require-file check exact claims in the
converted output. --extract applies only to native .holy packages. --catalog
can name a bound APK catalog; an arbitrary replacement fails source binding.
Neither form installs files, hooks or dependencies into the target root.
.PP
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 resolution remains unimplemented.
Explicit cached
replacement uses db plan-update and db apply-update. These
commands do not yet implement JSON output. See holy.conf(5) for identity rules
and endpoint restrictions.
.SH 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; references copied from an
older transaction's other consumers do not keep packages alive.
.PP
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.
.PP
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. Output 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 installed entries without saved graphs
require migration before this analysis can prove reachability.
.SH INSTALLED CONFLICTS
conflict reads a consistent installed-state snapshot under the database's shared
lock and reports the capabilities two installed artifacts both offer. 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.
.PP
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/private/ARTIFACT-ID/ followed by usr/bin,
bin, usr/sbin or sbin, which is the shape a package's private layout has.
.PP
Two providers of one SONAME whose arch or libc records differ are abi-mismatch
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 stating a capability twice
is not a conflict, since there is only one owner.
.PP
Text output is one conflict line per finding, naming the kind, the capability, the
provider count, the reason and every provider, followed by a generation, installed,
capabilities and conflicts summary. Names use the metadata 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 generation, installed, capabilities and findings. Status
is 0 when nothing collides, 1 when a conflict was found, 2 for an invalid argument,
5 for an incomplete 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.
.SH 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 default root is /. With no
selector it prints the whole index, one artifact record followed by the paths it owns
and the capabilities it declares, ordered by artifact hash, by path and by
capability.
.PP
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 capability selector takes a kind and
a name and names the artifacts that declare 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.
.PP
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 final 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.
.SH 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 active 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/ARTIFACT-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.
.PP
When the installed manifest owns a matching path under
/usr/lib/holy/private/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 returns 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 requires 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.
.PP
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 altered payload
returns 4. An incomplete transaction returns 5.
.PP
--view PUBLIC=PRIVATE maps one selected package-owned regular file or directory
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
package 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.
.PP
--auto-view derives regular-file bindings from the selected installed manifest:
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.
.SS AppImage staging
appimage inspect accepts a type 2 ELF image, checks its x86 or x86_64 machine 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 unprivileged user and
an available unsquashfs command. The output contains original, 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 symlinks. It reports the AppRun entrypoint and
runtime probes that static inspection cannot close. No .holy is produced and
no package is installed. This operation does not claim that the extracted
program can run.
.PP
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.
.PP
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 reachable 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 converted 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.
.PP
Dependencies are derived from the payload. A DT_NEEDED the payload itself carries
through its own SONAME is satisfied privately and names no requirement; every
other name becomes one soname requirement. A link with an absolute 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 under is declared by the same manifest.
.PP
import returns status 3 after writing the output, because the package changes the
launch conditions and carries no image-level sandbox. The report 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 decision. NAME does not grant a registered
source identity.
.SS 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 payload's own ELF files. The
command reads the image without executing its runtime.
.PP
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 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 symlinks. It reports the command the manifest names
and runtime probes that static inspection cannot close. No .holy is produced and
no package is installed. This operation does not claim that the extracted program
can run.
.PP
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 cannot 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.
.PP
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 payload is no confinement.
The classification and conversion reports travel with the package as
/usr/share/holy/NAME/.
.PP
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 payload, so the path it named becomes one file requirement.
.PP
import returns status 3 after writing the output, because the package changes the
confinement and needs a base package no snapd provides. The report in the output
directory names the base requirement, the dropped confinement and interfaces, the
recorded hooks, the command-chain and environment records, the path views a link
needs and the scripts that carry an interpreter. NAME does not grant a registered
source identity.
.SS 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 rewritten, as is a manifest with no
literal version, no artifact URL or no SHA-256 digest.
.PP
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 different 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 member 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.
.PP
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.
.PP
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 installer and integration
keys, the requirements a bucket dependency became and any archive member the
payload refused. NAME does not grant a registered source identity.
.SS 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 InstallerUrl or no InstallerSha256. A WinGet
digest is written in upper case and is compared in lower case, so both forms verify.
.PP
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 declares no program path
records none, and the report says so rather than guessing one from the archive.
.PP
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 integration 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.
.SS Nix closures
import CAPTURE --source NAME --format nix --output NEW_DIRECTORY reads one
captured closure as text and writes a conversion directory holding the capture, 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 status 2 as well.
.PP
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 converter does not model is counted and reported, since
an unread reference is a requirement nobody counted.
.PP
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/private/NAME/store/STORE_PATH/, and one pass over
its own bytes confirms the references the capture declares: a reference the payload
carries is confirmed 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.
.PP
A reference to a store path the capture carries becomes one package requirement 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.
.PP
A store path that states an entry point gets a link at
/usr/lib/holy/private/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.
.PP
Nothing is executed. No Nix store, daemon, profile or sandbox is reproduced, 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.
.SS Solus eopkg
import FILE.eopkg --source NAME --format eopkg --output NEW_DIRECTORY reads one Solus
binary package and writes a conversion directory holding the artifact, 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.
.PP
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 requirement. 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 release 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 architecture 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 entry, a conflict, a
replacement and a declared capability are all named in the report and none of them
becomes a requirement.
.PP
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.
.PP
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.
.PP
Each runtime dependency becomes one exact package requirement on the package 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 provide becomes a SONAME requirement, and a package requirement on
the package name is recorded as well.
.PP
Nothing is executed. An install script, a COMAR object, a delta, a signature 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 different payload rather than a
patch, and a signature is recorded without being trusted, so the artifact is verified
by its own digest only. import returns status 3 after writing the output. NAME does
not grant a registered source identity.
.SS 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 proposal is what a holy-recipe(5)
manifest states in its output and split records.
.PP
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 declared 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.
.PP
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.
.PP
The heuristics are deliberately weak, because a file extension is not a sufficient
criterion. A header, an include directory and pkg-config metadata 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 reference 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.
.PP
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 neither, and two rules contradicting each other
are a decision by definition.
.PP
The command prints the path counts per output and one decision line per unsettled
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.
.PP
--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 objcopy --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.
.PP
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 artifact needs, so the strict
elf reader refuses it and this form still answers. A file that states no note, or is
not an ELF, returns 2.
.SS 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 .SlackBuild
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
.pacscript reaches the Pacstall converter, a name ending in .rb reaches the Homebrew
converter, a name ending in .scm reaches the Guix converter and a name ending in
.json, .yml or .yaml reaches the Flatpak converter through the same command,
while any other name reaches the pacman converter, and
import INPUT --source NAME --format pkgbuild|void|aports|slackbuild|rpmspec|debian|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.
.PP
The PKGBUILD converter carries pkgbase, pkgver, pkgrel, pkgdesc, url, license,
arch, epoch, depends, makedepends, checkdepends, optdepends and backup,
expands $pkgbase, $pkgname, $pkgver and $pkgrel inside source entries, 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= fragment is copied into the
payload and recorded as one postinstall hook. provides, 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.
.PP
The Void converter carries pkgname, version, revision, short_desc, homepage,
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_default, 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.
.PP
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 checkdepends 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.
.PP
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 travels 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 ownership rewrite, the user and group creation and the loader
and desktop cache helpers are reported as unresolved.
.PP
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 comparison travels with
the name, while Provides, Conflicts, Obsoletes and Recommends become x- records
because none of them is a requirement. The prep, build, install and check
sections become the matching phases, %setup, %autosetup 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.
.PP
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 substitution 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_OPTIONS, and every debhelper call, every
debhelper override and dpkg-buildpackage 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.
.PP
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
dependency atom is a category, a package and an optional comparison, so the
category 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 between 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 Manifest 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.
.PP
The Pacstall converter carries pkgname, pkgver, pkgrel, pkgdesc, url, license,
maintainer, repology, arch and gives, and expands $pkgname, ${pkgname},
$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
installed into the payload.
.PP
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 supplies 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 requirement, because the SDK that builds the modules and
the runtime the program runs on are two different ids. A module becomes one step in
module order: its build-options export env, the flag variables and append-path, its
commands keep their shell, and the make, autotools, autogen, cmake and meson
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 converted 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.
.PP
The Homebrew converter reads a formula as Ruby text and never evaluates it. The
class name becomes the package name, the description, homepage and license are
carried, the url and its sha256 become one pinned source, the revision 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.
.PP
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 requirement, 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
extracted 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 framework, 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.
.PP
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 concatenation 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.
.PP
The build system becomes the tools it runs. gnu-build-system becomes autoreconf when
the source carries configure.ac or configure.in, then configure 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 #:configure-flags, #:make-flags or #:install-flags argument whose entries
are literal strings becomes the flags of the phase that takes them, and an argument
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.
.PP
All of these reports name every carried, preserved, helper, unknown and changed item
with its source file and line range, and their status is native 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.
.SS 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.
.SH EXIT STATUS
0 indicates success under the implemented checks; 1 indicates a failed local
fetch, extraction or check; 2 indicates invalid arguments, configuration or
package inspection. Diagnostics go to standard error except --json check
events, which go to standard output.
.PP
Preview returns 3 when valid requirements need provider decisions, 4 for path
conflicts and 6 when hooks or privileged modes need unsupported review. It
returns 1 on target-root I/O failure.
.SH SEE ALSO
.BR holy.conf (5),
.BR holy-package (5)