holypkg(8)
NAME
holypkg - Holy local package tooling prototype
SYNOPSIS
holypkg add local:FILE [--candidate local:FILE ...] [--choose ID=SHA256] [--associate-source ALIAS] [--associate SHA256=ALIAS ...] [--accept-arch SHA256 ...] [--accept-privileged SHA256 ...] [--skip-hooks SHA256 ...] [--root DIRECTORY] [--yes] [--noninteractive] holypkg add SOURCE:PACKAGE [--catalog MIRROR] [--candidate SOURCE:PACKAGE ...] [--candidate-provider SOURCE:KIND:NAME ...] [--candidate-local SOURCE=FILE.holy ...] [--choose ID=SHA256] [--answers FILE] [--accept-arch SHA256 ...] [--accept-privileged SHA256 ...] [--root DIRECTORY] [--prepare | --yes] [--noninteractive] holypkg up SOURCE:PACKAGE [--prepare] [--output NEW_FILE] [--catalog MIRROR] [--choose SHA256] [--arch ARCH] [--libc LIBC] [--accept-arch SHA256] [--accept-privileged SHA256] [--root DIRECTORY] [--yes] [--noninteractive] holypkg apply PLAN --sha256 PLAN_SHA256 [--root DIRECTORY] holypkg import INPUT --source NAME --format pacman|rpm|deb|slackware|apk|xbps|appimage|snap|scoop|winget|nix|eopkg|pkgbuild |void|aports|slackbuild|rpmspec|debian|gentoo|pacstall|flatpak --output NEW_DIRECTORY [--public-key FILE (apk only)] holypkg build RECIPE --output NEW_DIRECTORY [--environment host|clean|vm] [--work NEW_DIRECTORY] [--jobs N] [--yes] [--noninteractive] [--keep] holypkg convert PKGBUILD|APKBUILD|NAME.SlackBuild|NAME.spec|debian|NAME.ebuild|NAME.pacscript|NAME.json|TEMPLATE --source NAME --output NEW_DIRECTORY holypkg apt index Packages[.gz|.xz|.zst] --sha256 HASH --source NAME --base HTTPS_BASE/ --output NEW_DIRECTORY holypkg apt sync HTTPS_URL --sha256 HASH --source NAME --base HTTPS_BASE/ --output NEW_DIRECTORY [--ca-file FILE] holypkg apt sync-signed HTTPS_BASE/ SUITE COMPONENT ARCH --source NAME --keyring FILE --output NEW_DIRECTORY [--inrelease] [--files] [--ca-file FILE] holypkg apt sync-source ALIAS SUITE COMPONENT ARCH --root DIRECTORY --keyring FILE --output NEW_DIRECTORY [--inrelease] [--files] [--ca-file FILE] holypkg apt bind ALIAS SUITE COMPONENT INDEX_ARCH CATALOG --root DIRECTORY holypkg apt search QUERY --catalog DIRECTORY [--source ALIAS --root DIRECTORY] holypkg apt search QUERY --source ALIAS --suite SUITE --component COMPONENT --index-arch ARCH --root DIRECTORY holypkg apt info PACKAGE --catalog DIRECTORY [--source ALIAS --root DIRECTORY] holypkg apt info PACKAGE --source ALIAS --suite SUITE --component COMPONENT --index-arch ARCH --root DIRECTORY holypkg apt fetch NAME VERSION ARCH --catalog DIRECTORY --output NEW_DIRECTORY [--source ALIAS --root DIRECTORY] [--ca-file FILE] [--import] [--require-file /PATH] 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] holypkg appimage inspect INPUT holypkg appimage extract INPUT --output NEW_DIRECTORY holypkg snap inspect INPUT holypkg snap extract INPUT --output NEW_DIRECTORY holypkg apk index APKINDEX.tar.gz --source NAME --base URL/ --output NEW_DIRECTORY holypkg apk verify-index FILE --public-key FILE holypkg apk sync SOURCE REPO --root DIRECTORY --output NEW_DIRECTORY [--sha256 HASH | --accept-unsigned HASH] [--ca-file FILE] [--public-key FILE] holypkg apk bind SOURCE REPO CATALOG --root DIRECTORY [--accept-unsigned HASH] [--public-key FILE] holypkg apk search QUERY [--catalog DIRECTORY | --source SOURCE --repo REPO --root DIRECTORY] holypkg apk info PACKAGE [--catalog DIRECTORY | --source SOURCE --repo REPO --root DIRECTORY] holypkg apk providers soname:NAME [--catalog DIRECTORY | --source SOURCE --repo REPO --root DIRECTORY] holypkg apk fetch-provider soname:NAME ARCH --output NEW_DIRECTORY [--catalog DIRECTORY | --source SOURCE --repo REPO --root DIRECTORY] [--sha256 HASH] [--ca-file FILE] [--public-key FILE] holypkg apk fetch NAME VERSION ARCH --output NEW_DIRECTORY [--catalog DIRECTORY | --source SOURCE --repo REPO --root DIRECTORY] [--sha256 HASH] [--ca-file FILE] [--public-key FILE] [--import] [--require-soname SONAME] [--require-file /PATH] holypkg xbps index REPODATA --sha256 HASH --source NAME --base HTTPS_BASE/ --output NEW_DIRECTORY [--public-key FILE] holypkg xbps sync HTTPS_BASE/ ARCH --sha256 HASH --source NAME --output NEW_DIRECTORY [--ca-file FILE] [--public-key FILE] holypkg xbps sync-source ALIAS ARCH --root DIRECTORY --sha256 HASH --output NEW_DIRECTORY [--ca-file FILE] [--public-key FILE] holypkg xbps search|info QUERY --catalog DIRECTORY [--source ALIAS --root DIRECTORY] holypkg xbps search|info QUERY --source ALIAS --index-arch ARCH --root DIRECTORY holypkg xbps providers SONAME --catalog DIRECTORY holypkg xbps providers SONAME --source ALIAS --index-arch ARCH --root DIRECTORY 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] holypkg xbps fetch NAME VERSION ARCH --source ALIAS --index-arch ARCH --root DIRECTORY --output NEW_DIRECTORY [--ca-file FILE] [--public-key FILE] [--import] [--require-soname SONAME] holypkg repo mirror HTTPS_BASE/ --sha256 INDEX_SHA256 --output NEW_DIRECTORY [--public-key PUBLIC_KEY.pem] [--ca-file FILE] holypkg repo seal DIRECTORY [--key PRIVATE_KEY.pem] holypkg repo verify DIRECTORY --key PUBLIC_KEY.pem holypkg source plan --config FILE --root DIRECTORY holypkg source apply PLAN --sha256 HASH --root DIRECTORY holypkg source list --root DIRECTORY holypkg source show ALIAS --root DIRECTORY holypkg source catalog bind ALIAS MIRROR [--root DIRECTORY] holypkg sync SOURCE [--root DIRECTORY] [--output NEW_DIRECTORY] [--sha256 INDEX_SHA256 | --accept-unsigned INDEX_SHA256] [--commit GIT_COMMIT] [--ca-file FILE] holypkg sync APK_SOURCE [--repo REPO] --output NEW_DIRECTORY [--root DIRECTORY] [--sha256 HASH | --accept-unsigned HASH] [--ca-file FILE] [--public-key FILE] holypkg sync XBPS_SOURCE --arch ARCH --sha256 HASH --output NEW_DIRECTORY [--root DIRECTORY] [--ca-file FILE] [--public-key FILE] holypkg sync APT_SOURCE --suite SUITE --component COMPONENT --index-arch ARCH --keyring FILE --output NEW_DIRECTORY [--root DIRECTORY] [--inrelease] [--files] [--ca-file FILE] holypkg orphan [--root DIRECTORY] [--json] holypkg conflict [--root DIRECTORY] [--json] holypkg run SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC] [--auto-view] [--view PUBLIC=PRIVATE ...] -- COMMAND [ARGS...] holypkg docs --root DIRECTORY --output FILE holypkg config check FILE holypkg info local:FILE holypkg info SOURCE:PACKAGE [--repo REPO] [--arch ARCH] [--suite SUITE --component COMPONENT --index-arch ARCH] [--catalog MIRROR] [--root DIRECTORY] holypkg search QUERY [--source SOURCE] [--repo REPO] [--arch ARCH] [--suite SUITE --component COMPONENT --index-arch ARCH] [--file] [--fuzzy] [--catalog MIRROR] [--root DIRECTORY] holypkg verify local:FILE holypkg manifest local:FILE holypkg requirements local:FILE holypkg requirements local:FILE --json holypkg provides local:FILE holypkg provides local:FILE --json holypkg solve local:ROOT [local:CANDIDATE ...] holypkg solve local:ROOT [local:CANDIDATE ...] --choose REQUIREMENT_ID=SHA256 [--json] holypkg solve local:ROOT [local:CANDIDATE ...] --json holypkg fetch local:FILE --output DIRECTORY holypkg fetch local:FILE --extract --output DIRECTORY holypkg fetch SOURCE:PACKAGE [--catalog MIRROR] --output DIRECTORY [--extract] [--root DIRECTORY] holypkg fetch APK_SOURCE:PACKAGE --version VERSION --arch ARCH [--repo REPO] --output NEW_DIRECTORY [--catalog DIRECTORY] [--root DIRECTORY] [--ca-file FILE] [--public-key FILE] [--sha256 HASH] [--import] [--require-soname SONAME] [--require-file /PATH] holypkg fetch XBPS_SOURCE:PACKAGE --version VERSION --arch ARCH --output NEW_DIRECTORY [--catalog DIRECTORY] [--root DIRECTORY] [--ca-file FILE] [--public-key FILE] [--import] [--require-soname SONAME] holypkg fetch APT_SOURCE:PACKAGE --version VERSION --arch ARCH --suite SUITE --component COMPONENT --index-arch ARCH --output NEW_DIRECTORY [--catalog DIRECTORY] [--root DIRECTORY] [--ca-file FILE] [--import] [--require-file /PATH] holypkg fetch https://URL --sha256 SHA256 --output DIRECTORY [--ca-file FILE] holypkg pack DIRECTORY --output FILE.holy holypkg manifest generate DIRECTORY --output FILE holypkg split TREE --output NEW_FILE [--split OUTPUT GLOB ...] [--debug] holypkg elf FILE [--build-id] holypkg check local:FILE --root DIRECTORY holypkg check local:FILE --root DIRECTORY --json holypkg check [SOURCE:PACKAGE] [--root DIRECTORY] [--arch ARCH] [--libc LIBC] [--json] holypkg files SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC] holypkg why SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC] [--json] holypkg owner PATH [--root DIRECTORY] holypkg repair SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC] [--plan PLAN_SHA256] holypkg rm SOURCE:PACKAGE [--root DIRECTORY] [--arch ARCH] [--libc LIBC] [--yes] [--accept-broken] holypkg elf FILE holypkg scan local:FILE holypkg preview local:FILE --root DIRECTORY holypkg preview local:FILE --root DIRECTORY --json holypkg cache stage local:FILE --root DIRECTORY holypkg cache verify SHA256 --root DIRECTORY holypkg cache list --root DIRECTORY holypkg cache clean SHA256 --root DIRECTORY [--yes [--accept-unavailable]] holypkg db init --root DIRECTORY holypkg db status --root DIRECTORY holypkg db status --root DIRECTORY --json holypkg db reserve SHA256 --root DIRECTORY holypkg db cancel --root DIRECTORY holypkg db recover --root DIRECTORY holypkg db recover --abort-empty --root DIRECTORY holypkg db recover --continue --root DIRECTORY holypkg db recover --finish-apply --root DIRECTORY holypkg db preflight --root DIRECTORY holypkg db preflight --root DIRECTORY --json holypkg db plan --root DIRECTORY holypkg db approve PLAN_SHA256 --root DIRECTORY holypkg db recheck --root DIRECTORY holypkg db apply --root DIRECTORY holypkg db plan-set ROOT_SHA256 [CANDIDATE_SHA256...] [--choose ID=SHA256] [--source ARTIFACT=SOURCE_ID ...] [--accept-arch SHA256 ...] [--accept-privileged SHA256 ...] [--skip-hooks SHA256 ...] --root DIRECTORY holypkg db apply-set PLAN_SHA256 ROOT_SHA256 [CANDIDATE_SHA256...] [--choose ID=SHA256] [--source ARTIFACT=SOURCE_ID ...] [--accept-arch SHA256 ...] [--accept-privileged SHA256 ...] [--skip-hooks SHA256 ...] --root DIRECTORY holypkg db recover --finish-set|--continue-set --root DIRECTORY holypkg db configure-plan SHA256 --root DIRECTORY holypkg db configure-apply PLAN_SHA256 SHA256 --root DIRECTORY holypkg db configure-recover SHA256 --retry --root DIRECTORY holypkg db repair-plan SHA256 --root DIRECTORY holypkg db plan-update OLD_SHA256 NEW_SHA256 [--accept-arch NEW_SHA256] [--accept-privileged NEW_SHA256] --root DIRECTORY holypkg db apply-update PLAN_SHA256 OLD_SHA256 NEW_SHA256 [--accept-arch NEW_SHA256] [--accept-privileged NEW_SHA256] --root DIRECTORY holypkg rollback TRANSACTION [--root DIRECTORY] [--apply PLAN_SHA256] [--accept-arch ARTIFACT_SHA256] [--accept-privileged ARTIFACT_SHA256] holypkg db recover --update --root DIRECTORY holypkg db repair SHA256 --plan PLAN_SHA256 --root DIRECTORY holypkg db recover --repair --root DIRECTORY holypkg db check SHA256 --root DIRECTORY holypkg db check --all --root DIRECTORY holypkg db check SHA256|--all --root DIRECTORY --json holypkg db rm SHA256 [--accept-broken] --root DIRECTORY holypkg db owner PATH --root DIRECTORY holypkg repo index DIRECTORY holypkg repo list DIRECTORY holypkg repo requirements DIRECTORY NAME holypkg repo search DIRECTORY NAME holypkg repo search DIRECTORY NAME --fuzzy holypkg repo search-file DIRECTORY ABSOLUTE_PATH holypkg repo search-file DIRECTORY NAME_OR_PATH --fuzzy holypkg repo solve DIRECTORY NAME [--json] holypkg repo solve DIRECTORY NAME --choose REQUIREMENT_ID=SHA256 [--json] holypkg repo providers DIRECTORY KIND NAME holypkg repo providers DIRECTORY KIND NAME --json holypkg repo seal DIRECTORY holypkg repo fetch DIRECTORY SHA256 --output DIRECTORY
DESCRIPTION
Local pacman, RPM, Debian, Slackware, APK and XBPS binary import
import snapshots a local pacman tar archive, RPM v4 or v6 package, Debian .deb, Slackware package, APK v2 or XBPS package, retains its exact bytes as NEW_DIRECTORY/original and creates verified LZ4-frame .holy outputs. The output directory must not exist. --format selects the adapter explicitly; --source records provenance only and grants no registered source identity or trust. The command does not install files or execute package code. Without an explicit APK public key, each output records verification=unverified. An explicit key verifies the APK signature but does not grant a registered source identity.
--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.
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.
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.
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.
--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.
apt indexaccepts a local Debian Packages index with an explicit SHA-256 pin. It keeps the compressed source bytes in original and parses Package, Version, Architecture, Filename, Size, SHA256, Depends, Pre-Depends and Provides. Duplicate package/version/architecture entries, unsafe paths, missing hashes, empty indexes and malformed stanzas fail conversion. The catalog records its source name, HTTPS base URL, pin and package count. The source name is provenance, not a registered source-id. The output directory must not exist.apt syncdownloads 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.apt sync-signeddownloads 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.--inreleaseselects 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.--filesalso 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.apt sync-sourcereads 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.
The common sync APT_SOURCE command uses the signed Release verifier and binds its result. It requires suite, component, index architecture, keyring and output. --inrelease selects a clearsigned Release; --files includes the signed Contents index. Common search, info and fetch require the same three catalog coordinates and verify the binding. Common fetch also requires the package version and architecture; --import and --require-file keep the APT payload checks. A supplied --catalog must resolve to the bound directory.
apt searchandapt inforead 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.apt search /PATH --filelooks 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.apt fetchselects 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.
--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.
--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.
--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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The reference metadata format is https://alpm.archlinux.page/specifications/PKGINFO.5.html.
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.
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.
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.
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.
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.
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.
missing-man identifies packages with no included regular page. omitted-man lists unsupported page names/codecs. The final summary records generation, package/page/alias counts and missing/omitted coverage. A successful bundle can contain those explicit omissions; inclusion does not assess a manual's semantic completeness. Sources and manifests are limited to 16 MiB per file; decoded pages are limited to 64 MiB. Empty or NUL-containing page bodies fail.
The command builds a temporary file beside FILE and publishes without 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.
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.
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.
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.
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.
Host build
The core, the package manager and the installer are C99, and the build is make with the ordinary CC, CPPFLAGS, CFLAGS, LDFLAGS and LDLIBS. make CC=tcc, make CC=gcc and make CC=clang each compile it, and make check-cc proves it: the target compiles the core with tcc, gcc and clang in turn, packs a package with the result, verifies it and lists its payload, then restores the default build. A missing toolchain returns 6 instead of a green run, and a toolchain that breaks the C99 build fails the target with its own compiler output.
Static core build
make bootstrap-mdevd ARCH=i686|x86_64 INPUTS=DIRECTORY OUTPUT=DIRECTORY builds a musl-static mdevd 0.1.8.2 package with skalibs 2.15.1.0. The 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.
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.
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.
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.
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.
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.
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.
make check-static-core STATIC_HOLYPKG=FILE BUSYBOX_PACKAGE=FILE copies that binary into a disposable root without dynamic libc directories. Inside the chroot, holypkg verifies and caches the supplied BusyBox package, installs, checks and removes it twice using the cached artifact. The original package is removed after caching. A BusyBox shell probe runs after each install. doas creates the chroot and drops to the caller's numeric UID/GID. For an ELF32 i686 client, the fixture uses setarch i686 so the process sees the i686 host personality while the x86_64 kernel executes it. This does not test booting an i686 kernel. Both artifact hashes are printed on success. This verifies the local static package path; removal and restoration of real dynamic libc packages, network recovery and dinit boot still require their own tests.
Passing DINIT_PACKAGE=FILE to check-static-core also installs, checks and removes the complete dinit package, including its symlinks, with holypkg running inside that libc-free chroot. The fixture removes the input after caching and reports the tested artifact hash. Service control is covered by check-bootstrap-dinit.
make check-static-network STATIC_HOLYPKG=FILE BUSYBOX_PACKAGE=FILE 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.
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.
Package operations
The config check command checks source and rule configuration without changing the system. General package installation, foreign import, source sync and multi-source resolution are not available yet. This executable is independent of the separate owenewans/holypkg project.
Pinned HTTPS fetch downloads one native .holy into an existing, non-writable- by-others output directory. It requires an expected SHA-256, HTTPS including redirects, certificate validation and an URL without 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.
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.
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.
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.
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.
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.
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.
db configure-plan reviews native postinstall records with the syntaxhook postinstall INTERPRETER RELATIVE_SCRIPT_PATH sha256 DIGESTin 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 asold-instancebefore 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.
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.
why SOURCE:PACKAGE reads the installed graph and prints one shortest saved dependency path from an explicit root to that package. A package with no path from an explicit root is marked orphan. The command reads no cached archives and makes no changes. It refuses incomplete or malformed installed graphs and missing provider instances. JSON output uses schema holy-why-1 with path, orphan and summary events. The path reports package identities; exact requirement names remain in the saved graph and are not included in this view.
add local:FILE stages a verified native artifact in the selected target-root cache, builds a read-only package-set plan and shows its selected packages, providers and plan hash. --candidate adds local archives to the resolver pool; only selected packages are installed. --choose binds one stable requirement ID to an exact provider artifact hash. The default root is /. Local archive metadata cannot assign a trusted source-id.--associate-source ALIASis an explicit confirmation that the root local artifact belongs to an active registered source. The manager resolves the alias to its immutable source-id under the database lock and binds that ID into the reviewed plan. This flag does not infer provenance from archive metadata, verify a publisher signature or associate the candidate dependency archives. The association requires a registered active source and is checked again before apply.--associate SHA256=ALIASbinds 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.--accept-arch SHA256confirms placement of one selected artifact whose architecture differs from the native target. It does not change the target metadata or prove execution.--accept-privileged SHA256confirms 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.
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.
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--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.--candidate-provider SOURCE:KIND:NAMEselects 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.--candidate-local SOURCE=FILE.holyadds 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.
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.
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.
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.
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.
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.
Database recheck compares an approved record with a fresh verified cache snapshot and target-root collision preview under a shared database lock. It returns 5 without an approved record, 3 if the plan digest differs and 4 for a path conflict. The check changes neither database nor rootfs. Other processes can change rootfs paths after recheck; it cannot replace the writer-locked validation required by apply.
Plan construction checks each payload path against installed manifests as well as rootfs. An absent file remains owned until its installed claim is removed; another package cannot acquire that path by recreating it. Shared directory claims remain permitted. These checks read manifests directly; generated ownership indexes are not required for correctness.
Database apply installs only an approved linux/nolibc package with empty hooks and requirements. HOLY/transform is retained as a record of completed changes and is never executed by apply. Its payload may contain ordinary regular files, including native static ELF executables, relative symlinks, direct hardlinks and directory entries. Existing directories must match the manifest's mode and numeric owner/group. Missing parents require explicit directory entries in that package; apply creates them in parent-first order before writing files. A changed directory is rejected before writing a journal. No absolute symlink, privileged mode or file collision is 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.
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.
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.
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.
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 "-".
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.
The internal file planner records retained, added, replaced and removed paths with stable IDs and archive hashes. Unsupported entries remain in the record and prevent application. Recovery checking accepts exact old or new objects and rejects unrelated changes.
db apply-update PLAN_SHA256 OLD_SHA256 NEW_SHA256 --root DIRECTORY applies that cached replacement after reconstructing and comparing the approved plan under the writer lock. It preserves explicit/dependency reasons and source identity, rewrites each installed consumer's own provider edges, and increments the database generation once. A changed root, registry, generation, artifact or installed state requires a new preview.
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.
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.
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.
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.
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.
The internal resolver can also validate a complete proposed artifact set. It requires every supplied artifact, including disconnected consumers and dependency cycles. Missing requirements fail the set; ambiguous provider edges still require a decision. The caller must construct the installed set after replacement and preserve instance reasons and source bindings. This interface does not discover update candidates or apply transactions.
Static executable installation verifies ELF architecture and nolibc facts; it does not execute package programs. The caller may run an installed 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.
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.
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.
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.holypkg repair SOURCE:PACKAGEresolves 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The verify command checks regular DATA files against the supported HOLY/files records, including size, mode, numeric owner and SHA-256. It checks symlinks, directories and direct hardlinks, rejecting unsupported objects. It does not install or authenticate packages; the two read passes do not provide a stable filesystem snapshot.
The manifest command stages a local archive, verifies the entire supported payload and HOLY/files, then lists validated objects in path order. Each line contains type, escaped relative path, mode, numeric UID/GID and size; files include SHA-256, links include targets and grouped hardlinks include their group. Fields are borrowed from the verified snapshot during 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.
The requirements command stages and verifies a regular archive, then reads HOLY/deps using the holy.conf lexer. Its current typed subset accepts lines of eleven tokens:
require ID CONSUMER KIND NAME ARCH LIBC RELATION VERSION ORIGINAL EVIDENCE
KIND is package, package-or, file, command, soname, symbol-version or build. For package-or, NAME contains Debian branches as NAME@RELATION@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.
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.
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.
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.
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.
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.
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.
The provides command stages and verifies a native archive, then reads HOLY/provides as typed claims. Each supported line has seven tokens:
provide KIND NAME ARCH LIBC VERSION EVIDENCE
KIND accepts package, file, command, soname, symbol-version or build. ARCH accepts any, x86, x86_64 or noarch; LIBC accepts any, glibc, musl or 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.
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.
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.
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.
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.
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.
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.
A PT_NOTE GNU build-id note is reported as build-id in lowercase hex when the file states one, and omitted otherwise. A second note of the same kind, an empty note and a note longer than 32 bytes are not identities this reader states, so a file carrying one of them is rejected. elf FILE --build-id reads the note section instead of the loadable segments, which is how a separate debug file is checked after a split.
The local scan command stages a regular .holy input in a private file, 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.
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.
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.
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.cache listreads 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.cache clean SHA256prints 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.
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.
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.
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.
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.
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.
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.repo requirementsreads 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.
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.
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.
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.
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.
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.
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.
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.
SOURCE REGISTRATION
source plan reads the explicit configuration and writes a complete proposed registry to stdout. It reports alias changes, origin changes, additions and deactivations on stderr. A change to trust, key, parent, family or priority under the same source ID is reported as policy-change. Review and save the plan, then supply its SHA-256 to source apply. An existing initialized database is required. No source command downloads packages or runs package code.
The plan binds the previous registry digest, installed database generation and database device/inode. Apply stages the plan, verifies its approved 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The command does not remove files, run package code, fetch artifacts or need the package cache. Each candidate includes its artifact hash, package name and the reason unreachable-from-explicit. Names in text output use the metadata lexer's byte escapes. JSON names preserve valid UTF-8. This is graph analysis, not a payload or application-health check; use db check for file integrity.
With --json, holy-orphan-1 candidate events contain artifact, name, reason and generation. A final summary contains generation, installed, explicit, reachable and orphans counts. Candidates are ordered by artifact hash. 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.
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.
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.
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.
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.
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.
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.
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.
--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.
--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.
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.
import INPUT --source NAME --format appimage --output NEW_DIRECTORY uses the same extraction, records NAME as unverified provenance and then emits one native package beside the review bundle. The package name is the image file name without its .AppImage suffix, and a name the manifest cannot record is refused with status 2 rather than rewritten. The version is read from the desktop entry the image ships, X-AppImage-Version or Version; a payload that states none records version 0 and the report says so. The arch and libc come from the payload's own ELF files, and a payload that mixes architectures or runtimes is refused with status 3, because one .holy records one of each.
The whole AppDir travels under /usr/lib/holy/private/NAME/appdir/, so no public path is claimed for the image's own files. The entry point is 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.
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.
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.
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.
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.
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.
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/.
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.
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.
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.
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.
A Scoop dependency names an app of the same bucket, so each one becomes a package requirement the target has to satisfy from a source that has it. The installer, pre_install, post_install, uninstaller, env_add_path, env_set, persist, shortcuts, psmodule and suggest keys are dropped and counted, because a PowerShell step and a Windows integration belong to a Windows installation; checkver and autoupdate are counted too, since a bucket owns its own index. Nothing in the artifact is executed, and no Wine requirement is recorded, because a package cannot promise a runtime this manager does not provide.
import returns status 3 after writing the output, because the payload is a program for another operating system. The report names the artifact and its digest, the directory and program the manifest states, the dropped 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The architecture decides the placement. noarch, x86_64, i686, aarch64 and armv7hl name an architecture this manager places, and anything else needs classification, so the import returns status 3 without writing a package.
The install tar travels whole under /usr/lib/holy/private/NAME/eopkg/. A Solus package owns a system layout, and this manager records that layout rather than claiming it, so the payload installs under a private path and claims no public bin path. A leading ./ names the same path and is dropped; a member that names .. is refused rather than placed, and the conversion is refused with status 1. A payload that is not an archive this reader can read is refused the same way. A member that is neither a regular file, a directory nor a symbolic link is skipped and counted, and a symbolic link whose target leaves the private tree becomes a recorded file requirement on the path it named.
Each runtime dependency becomes one exact package requirement on the 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.
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.
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.
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.
An explicit --split rule outranks a heuristic: the first rule that matches a path assigns it, and two rules naming different outputs for one path leave that path a repeated decision rather than a guess. A rule may only name one of the three outputs the proposal declares, and a rule naming any other output is refused with status 2.
The heuristics are deliberately weak, because a file extension is not a sufficient criterion. A header, an include directory and pkg-config 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.
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.
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.
--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.
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.
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 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 --output NEW_DIRECTORY names the family explicitly. None of these commands installs a package, builds one, or grants NAME a registered source identity; the report records it as provenance.
The PKGBUILD converter carries pkgbase, pkgver, pkgrel, pkgdesc, url, 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Prepared source set
add SOURCE:PACKAGE --prepare prints the selected set, source bindings and plan hash, then returns success without installing files or writing a transaction. It can stage verified artifacts in the target cache. It cannot be combined with --yes. The caller must preserve the catalog and artifact hashes when carrying this selection into another target root.
EXIT STATUS
0 indicates success under the implemented checks; 1 indicates a failed 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.
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.
SEE ALSO
holy.conf (5),holy-package (5)