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

file man/holy-image.7

.TH HOLY-IMAGE 7 "September 2026" "Holy" "System Overview"
.SH NAME
holy-image \- image build and libc recovery boot tests
.SH BUILD
make bootstrap-kernel packages an existing x86 kernel image as linux.holy.
KERNEL_IMAGE, KERNEL_VERSION, ARCH and OUTPUT select its inputs and output;
MODULES_DIR optionally names a staging tree for that release. The builder
checks the x86 boot header, matches every .ko to modules.dep, verifies the
package and scans each module's architecture and vermagic. It records the
input and artifact hashes. This target packages an existing kernel; QEMU
boot acceptance belongs to holygetiso.
.PP
make bootstrap-image builds a live ISO and runs its QEMU boot test. ARCH defaults
to x86_64. ARCH=i686 supports BIOS optical ISOs with ROOT_STORAGE=ram or ext4
and an independently bootable BIOS GPT disk with ROOT_STORAGE=gpt-ext4.
IMAGE_BOOT_TEST=build-only produces the image without a QEMU run, records
"result untested" and returns 6. If the selected QEMU binary is missing,
the builder takes the same path. Neither case counts as a release boot gate.
The i686 ISO boots through El Torito; it is not a hybrid USB image.
The i686 profile requires an i686 kernel and matching static
BusyBox, dinit, mdevd, holypkg and C compiler inputs. The builder checks
package and executable architectures before installation. The dual-libc profile
also requires matching i686 glibc and musl runtime packages and C compilers.
The GPT profile has no i686 UEFI loader; its tested boot path is BIOS.
IMAGE_PROFILE defaults to dual-libc; static-core explicitly selects the earlier
profile without dynamic libraries. LIBC_BOOT_STATE is present by default.
For dual-libc, glibc, musl or both remove those runtime payloads and their loader
links before creating initramfs, while retaining installed records and cached
.holy artifacts. These are intentional damage fixtures, not package removals.
LIBC_BOOT_STATE=remove-both requires an ext4 or gpt-ext4 dual-libc root. The
first guest boot runs both dynamic probes, then holypkg removes both runtime
packages with an explicit broken-dependency decision and reboots. Static
BusyBox, dinit and holypkg run on the second boot, report broken providers,
install the same two cached .holy artifacts through reviewed plans and repeat
the probes. The QEMU report requires both boot contracts and an unchanged
read-only base image.
ROOT_STORAGE defaults to ram. ext4 selects a persistent recovery test for
dual-libc and requires mke2fs and qemu-img. The kernel must include ext4 and
virtio-blk, and the BusyBox package must include switch_root, sync and reboot.
gpt-ext4 selects an independently bootable GPT disk with the same ext4
recovery contract. It additionally requires sfdisk, mkfs.fat and mtools;
xorriso is only required for ISO profiles.
NETWORK_RECOVERY defaults to off. The explicit fixture value requires a
dual-libc RAM image with LIBC_BOOT_STATE=both. The builder omits both libc
archives from the guest cache, retains copies for a local HTTPS fixture,
and packages a generated test CA. The guest assigns the fixture address with
static BusyBox ip, downloads both archives with static holypkg and verifies
their SHA-256 digests before missing-only repair. The test CA key stays in the
build output and is not included in the image or documentation bundle.
The following make variables are required:
.TP
STATIC_HOLYPKG
Previously built musl-static holypkg executable.
.TP
STATIC_HOLYINSTALL
Previously built musl-static holyinstall executable. The builder checks its
architecture and nolibc runtime, packages it with holyinstall(8), and the
guest calls its disk subcommand on the disposable QEMU target and tests
package plan/apply in a separate target root.
.TP
STORAGE_TOOLS_PACKAGE
Native musl-static package with sfdisk, mkfs.fat, mke2fs and the Limine
command, required by INSTALL_TEST=1. make bootstrap-storage builds it from
pinned util-linux, dosfstools, e2fsprogs and Limine inputs.
.TP
DOAS_PACKAGE
Native musl-static OpenDoas package with a root-owned setuid executable,
required by INSTALL_TEST=1. The guest stages it from the live cache and
installs it in the reviewed holyinstall set plan with approval bound to the
artifact SHA-256.
.TP
STATIC_CC
Path to the musl C compiler driver for the early init helper.
.TP
BUSYBOX_PACKAGE, DINIT_PACKAGE, MDEVD_PACKAGE
Native .holy outputs of the matching bootstrap targets.
.TP
GLIBC_PACKAGE, MUSL_PACKAGE
For dual-libc, the native bootstrap libc packages with private literal loader
paths. bootstrap-glibc and bootstrap-musl produce the tested input layouts.
.TP
GLIBC_CC, MUSL_CC
Compilers for the two dynamic C fixtures. GLIBC_CC defaults to gcc; MUSL_CC
must name a musl compiler. The image build checks their actual runtime
requirements against the selected libc payloads before installation.
.TP
KERNEL_IMAGE, KERNEL_VERSION
Local Linux kernel image and its expected uname release. The selected kernel
must include initramfs/gzip, devtmpfs, proc, sysfs, tmpfs, devpts and serial
console support. The local kernel-image profile includes no kernel modules.
The initramfs excludes kernel modules. A source kernel package may install
modules in the rootfs; the dummy.ko fixture is loaded during the QEMU boot
probe when present with its matching modules.dep entry.
.TP
LIMINE_DIR
Directory containing limine-bios.sys, limine-bios-cd.bin and
limine-uefi-cd.bin. x86_64 also requires BOOTX64.EFI. Use the matching host
limine tool.
.TP
OUTPUT
New output directory. Existing directories are refused.
.PP
Host tools include unshare with user namespaces, a C toolchain, dracut,
xorriso, Limine, GNU file tools, gzip, cpio, Python 3 and QEMU.
DRACUT_BASE selects dracut's helper directory; the default is
/usr/lib64/dracut. Compilation and installation run as mapped root in a user
namespace owned by the ordinary caller. Host root invocation is refused.
.SH INPUTS AND ROOT
The builder snapshots the selected native packages, normalizes numeric ownership
to root and directory modes to 0755, regenerates manifests and records the
parent hashes and normalization in origin. These are new unsigned local
artifacts. The original archives remain in inputs/.
For the older BusyBox bootstrap package, its embedded-musl license moves to
usr/share/licenses/busybox/musl.COPYRIGHT, with the mapping recorded in origin.
The dynamic musl package owns its own license file.
.PP
The static core binary, selected kernel, Limine data and Holy boot configuration
also become native packages. The holy-base metapackage requires the resulting
eight packages, including holyinstall. INSTALL_TEST=1 also selects the doas
package and records its setuid approval. The dual-libc profile adds the two
runtime packages and two C probe packages. holyinstall asks holypkg to resolve the selected set, writes
install.preview and a frozen install.plan, then applies that plan with one
writer lock and one generation change into root/. The preview records selected
providers and requirements; the plan records artifacts and architecture approvals.
The builder prepares the profile's directories first. It does not extract
foreign distro roots or modify the host package database.
.PP
For ROOT_STORAGE=ext4, mke2fs copies that prepared root into a 512 MiB raw
root.ext4 without mounting it on the host. Limine passes holy.root=/dev/vda
and holy.rootfstype=ext4. holy-init mounts the disk, moves runtime mounts and
executes BusyBox switch_root followed by dinit. The ISO still provides kernel
and initramfs; this is not yet an independently installed bootable disk.
.PP
ROOT_STORAGE=gpt-ext4 creates a new 1 GiB regular disk.raw. The builder
uses holyinstall disk plan/apply and includes the reviewed plan hash in the
boot plan. The GPT contains a 1 MiB BIOS Boot partition, a 256 MiB FAT32 ESP and
a 765 MiB ext4 root. It verifies the partition layout, copies the kernel,
initramfs and Limine configuration to the ESP, reads those files back and
compares their bytes. It then uses holyinstall disk finalize-plan/apply to
bind the ESP, root and disk images by SHA-256 before copying their bytes into
the partitions. The finalize journal records the write stages. Limine BIOS
stages use partition 1; on x86_64 the UEFI loader
uses EFI/BOOT/BOOTX64.EFI. The kernel mounts /dev/vda3. Host filesystems are
not mounted and physical disks are not accepted as output.
.PP
The GPT profile passes holy.esp=/dev/vda2. holy-init mounts that FAT partition
at /boot before starting dinit, using nosuid,nodev,noexec and masks matching
the kernel package's file and directory modes. The kernel must include vfat,
the default FAT codepage and NLS charset. The boot probe checks the mount,
kernel/initramfs/config presence and a plan-bound write that survives reboot.
Installed package checking validates the mounted kernel payload against its
recorded manifest. This exposes the actual boot files to subsequent operations;
coordinated kernel/initramfs update transactions remain to be implemented.
.PP
The profile installs merged-/usr links, a locked root account and the dinit
services boot, console, mdevd, coldplug and holy-test. mdevd reports readiness
through a pipe before synchronous coldplug starts. The fixture checks that
coldplug applied the /dev/null permission rule. This is device-manager
coverage, not client libudev compatibility.
.SH INITRAMFS AND ISO
The private dracut module copies the installed root with preserved ownership
and file contents. dracut includes only the Holy module, with no host-only
configuration, host runtime dependencies, microcode or kernel modules.
The builder points dracut's sysroot at the installed root. It extracts the
finished initramfs, compares payload bytes, modes and symlinks with that root,
and checks each ELF with holypkg. Only dracut's module list, build parameters
and generated loader caches may be additional files. The audit rejects
unexpected loader aliases. dracut uses ldconfig -X so package symlinks remain
unchanged. The audit also rejects
non-static ELF outside the five declared libc and probe paths in dual-libc;
static-core rejects all non-static ELF. The prior package-set scan validates
the libc/probe graph and payload manifests. Omitted files are listed in the
build record and remain absent from the image until guest recovery.
holy-init mounts the live runtime filesystems and executes dinit.
Limine loads the kernel and initramfs from the ISO. The default entry selects
the guest test service with holy.test=1. Remove that argument in Limine's
entry editor to reach the BusyBox console service.
.PP
The output includes packages/, inputs/, root/, initramfs.img, holy-ARCH.iso,
build.log, build.record, root-check.record, a frozen plan and its boot-plan
SHA-256. The record lists artifact hashes and the build result. Input
retention permits repeating an experiment; bit-identical ISO reproduction
is not established.
.SH INSTALLER VM FIXTURE
INSTALL_TEST=1 selects an x86_64 static-core RAM ISO whose default service
installs the selected base artifacts on a separate QEMU guest disk. The
runner creates a blank, read-only 1 GiB raw disk and attaches a writable
qcow2 overlay to the guest. The live guest uses holyinstall to partition
that disk, create FAT32 and ext4 filesystems, and install Limine's BIOS
stage. Its static storage tools come from STORAGE_TOOLS_PACKAGE, which the
builder installs into the live image but excludes from the installed base.
The guest mounts the target root, stages its local .holy
archives, reviews and applies one holyinstall package plan containing doas
and its setuid approval. When the live image has configured sources, the guest
registers the same source IDs in the target root, copies embedded pinned
mirrors, and binds source-attributed artifacts in that plan. It verifies the
installed source records before declaring the package set committed. The
source config remains at /etc/holy.conf on the installed disk. It generates the
installed man bundle, and copies kernel, initramfs and Limine files from the
ISO to the ESP. The runner shuts down that guest after serial success markers,
then forks the installed overlay for separate BIOS and UEFI disk boots. Both
guests check dinit, BusyBox, holypkg, package state, documentation and the
mounted ESP. The install-test image contains a disposable local user and a
static login probe. Each installed boot checks rejection of an incorrect
password and an authenticated shell with UID 10001. The same PTY probe enters
the password requested by doas and checks UID 0 from the policy's selected
BusyBox command. The fixture account and scoped doas.conf are
only present when INSTALL_TEST=1. The UEFI boot uses fresh writable firmware
variables.
.PP
The holy-install-vm-7 report records each QEMU command, observed and missing
serial markers, serial hashes,
ISO and disk hashes, the guest disk-plan hash, base-image integrity and
checks reached by the guest. The i686 BIOS install-to-disk fixture passed under
QEMU/TCG. Its report lists the account menu, PAM/NSS, network and i686 UEFI as
untested. INSTALL_FIRMWARE=bios runs only BIOS and records UEFI as untested;
the x86_64 default both mode requires UEFI_CODE and UEFI_VARS,
which default to the local QEMU edk2 images. This fixture verifies package installation and
boot from a separate disk; it does not claim the whole interactive installer
acceptance matrix. Run an existing fixture ISO with
make check-install-vm ARCH=i686 ISO=FILE BOOT_PLAN=SHA256 for i686, or omit
ARCH for x86_64. The i686 install fixture requires a BusyBox package with
chown, login, getty, su, passwd, adduser and addgroup applets.
.SH QEMU
make check-qemu ARCH=x86_64 ISO=FILE BOOT_PLAN=SHA256 runs the test separately.
IMAGE_PROFILE defaults to dual-libc. LIBC_BOOT_STATE must match the image's
declared initial state. The runner requires the corresponding recovery markers;
a static-only boot cannot pass a dual-libc test.
ARCH=i686 chooses qemu-system-i386 and requires an i686 guest image.
The builder reads the x86 kernel boot header and rejects a kernel with the
other target architecture before it creates an image.
REPORT_DIR chooses an existing report parent directory, defaulting to the
system temporary directory. QEMU_TIMEOUT bounds each boot in seconds (1..600,
default 120). QEMU_PROBE_TIMEOUT bounds each guest stage (1..600, defaulting
to QEMU_TIMEOUT). The guest emits stage-start markers; a repeated marker does
not renew its deadline. The runner tracks repeated stages within each boot;
the report names the boot and stage that ran or timed out.
QEMU_ACCEL=auto selects usable KVM or TCG; kvm and tcg force
the accelerator, with unavailable KVM returning 6.
.PP
FIRMWARE=bios is the default. FIRMWARE=uefi requires UEFI_CODE and UEFI_VARS.
The runner copies both firmware inputs and gives the guest a new writable
VARS file. It copies and hashes the ISO before starting QEMU. Networking is
disabled by default, and no host writable disks or directories are attached.
For NETWORK_RECOVERY=fixture, the runner creates a private network namespace,
raises its loopback, serves the two frozen artifacts over HTTPS and connects
QEMU user networking only to that namespace. Its host has no external route.
The guest uses 10.0.2.15/24, resolves fixture.holy.test through the fixture
DNS server and fetches both archives by that HTTPS name. The report records
DNS queries, the CA and artifact hashes, HTTP requests and recovery markers.
This fixture does not test a public CA bundle or external network access.
.PP
ROOT_DISK supplies a self-contained regular raw or qcow2 disk image for the
persistent dual-libc test. An external qcow2 backing chain is rejected. The
runner copies the input to a read-only base, creates a qcow2 overlay and
attaches only that overlay through virtio-blk. It verifies the copied base
hash after QEMU exits and records the overlay hash. QEMU_KEEP=1 retains the
overlay and copied inputs for inspection. The default removes those temporary
files after writing the report and serial log. Copying a live
input is marked unverified for consistency; the caller must provide a stopped
image or stable snapshot for a precise trial. ROOT_IMAGE
alone remains an optional report input and does not attach a disk.
.PP
The guest verifies that / is ext4, completes recovery and its package probes,
records the plan ID under /var/lib/holy-boot-test/reboot, calls static sync
and requests reboot. On the second boot it requires that witness and intact
restored libc; it repeats PID 1, shell, device, dynamic/IPC and package probes
without another libc repair. The runner keeps each boot's markers separate,
rejects repeated or out-of-order boot numbers and requires both complete
contracts. Each boot has its own QEMU_TIMEOUT deadline. RAM tests still use
one boot and QEMU's no-reboot mode.
.PP
Guest markers must confirm the expected plan and architecture, dinit as
PID 1 by /proc/1/exe, a BusyBox shell probe, mdevd/coldplug and a complete
local two-package dependency-set install/check/remove, including refusal to
remove the referenced provider first. The guest checks the critical executables'
static ELF classification. For static-core it also checks absence of the four
expected dynamic loaders. For dual-libc it verifies the declared initial
presence/absence of the runtime files, diagnoses broken providers, reads the
local LZ4 archives and executes missing-only repair inside the guest. It runs
allocation, thread and clock probes with each libc, checks both standard loader
links and exchanges data through a pipe in both directions between the two ABIs.
KERNEL_VERSION additionally requires the
matching release marker. A failure marker overrides pass markers.
The runner sends QMP quit after the probes or a boot timeout, waits up to
five seconds, then kills a process which did not exit. Process launch or
exit status alone cannot pass a boot test.
.PP
Each run retains serial.log, QEMU output, a text report and report.json
(holy-qemu-report-2). Reports include argv, accelerator, firmware, input
hashes, elapsed time, stage deadlines, exit reason and per-marker status.
The report records whether temporary inputs and the writable overlay remain.
Persistent tests additionally record both boot results, the first-boot
completion time, root storage type and whether the read-only base stayed intact.
The probe list records each stage's elapsed time and pass/fail/unknown status.
Optional KERNEL_IMAGE, INITRAMFS and ROOT_IMAGE inputs add their hashes.
They identify caller-supplied files, not guest measurements of running code.
.SS Retained recovery matrix
make check-recovery-matrix REPORT=NEW_FILE REPORTS="REPORT_PATHS" validates
eight retained QEMU report.json files: present, glibc, musl and both initial
states, each under BIOS and UEFI. Each case must contain two complete ext4
boots. The validator checks per-boot serial evidence and reported checks,
including PID 1 identity after reboot. It rehashes the retained ISO, raw root,
recovery overlay and UEFI code snapshots. BIOS and UEFI runs of each initial
state must use the same plan, ISO and raw root. Missing or duplicate cases,
changed snapshots and incomplete second boots fail the matrix.
.PP
The command creates a new holy-recovery-matrix-1 JSON report containing each
input report and serial hash, image identities, acceleration and elapsed time.
The output must not exist. Run the guest tests before invoking this command;
the validator reads retained evidence and does not start new VMs. Local hashes
bind the retained files, not an authenticated publisher. Preserve each run
directory alongside the summary for revalidation.
.SH COVERAGE
BOOT_MEDIA=disk selects standalone disk boot in the QEMU runner and requires
ROOT_DISK. ISO must be empty; the runner attaches no CD-ROM. Each run uses
its own qcow2 overlay and records boot_media, two boot contracts and the
unchanged copied base. gpt-ext4 produces disk.raw, disk.plan, disk-layout.json
and limine.conf instead of an ISO. BIOS and UEFI remain separate test runs.
.PP
The bootstrap glibc package currently contains
libc.so.6 and its loader with licenses, not the complete SDK, locale archive,
NSS modules or auxiliary glibc libraries. The reference image still needs
the installer, ordinary networking and public CA packaging. The ext4 fixture
uses an ISO kernel;
gpt-ext4 loads its kernel and initramfs from the disk's ESP. Both test
recovery followed by reboot on a persistent root overlay. The GPT builder
is not the interactive installer. Kernel updates still require integration
with ESP contents and the generated initramfs.
Those gates remain separate from this boot result. The glibc/musl/both damage
fixtures retain package records; remove-both exercises package removal and
reinstallation after a persistent reboot.
The repository documentation bundle contains Holy pages. The image builder
uses holypkg docs after its package transaction to generate
/usr/share/holy/llm.txt from the installed man sources, including dinit.
This generated file is separate from package-owned source pages; its SHA-256
is recorded in the frozen image plan and /etc/holy/docs.sha256. The output
directory retains llm.txt and docs.record with the coverage summary. Packages
without man pages and unsupported page encodings appear in that bundle.
.PP
On each boot, the guest verifies the shipped bundle's digest and regenerates
it with static holypkg after any libc recovery. It compares the complete contents,
normalizing only the final summary's database generation because package
transactions between boots advance that counter. A documentation failure
fails the boot contract. The bundle contains attributed roff sources rather
than rendered upstream text. QEMU virtual devices do not establish physical
GPU support.
.PP
make check-image-docs IMAGE_DIRECTORY=DIRECTORY tests an ext4/dual-libc/both
build's documentation failures. It copies root.ext4 into two disposable disks,
changes the generated bundle in one and the installed holypkg man source in
the other, then boots each through the original ISO under BIOS/TCG. Both
guests must stop the boot contract at the documentation stage. The original
root image's hash must remain unchanged. The fixture requires debugfs and
the QEMU tools; it never edits the original image or mounts a host disk.
.SH HARDWARE
make check-hardware runs outside QEMU against a physical NVIDIA GPU using
Nouveau and Mesa NVK. It checks the kernel driver and DRM render node,
selects the NVK Vulkan device, presents 30 frames with vkcube, and measures
OpenGL frames with glxgears. glxinfo must report an accelerated NVIDIA
renderer. lspci, vulkaninfo, vkcube, glxinfo, glxgears, timeout, a display
session and a writable render node are required.
.PP
The default HARDWARE_SCOPE=holy requires a valid installed Holy database on
the running system. Missing inputs return 6. REPORT defaults to
out/hardware.json; the holy-hardware-1 JSON records probe commands, output,
elapsed times, driver selection and frame counts. A failure returns 1.
HARDWARE_SCOPE=host permits a diagnostic probe on another distribution and
marks release_gate=false. A host result cannot certify Holy's packages or
boot image. The frame probes establish presentation and an FPS count, not
pixel-correct rendering or broader game compatibility.
.SH SOURCES
Limine configuration and ISO layout:
https://github.com/limine-bootloader/limine/blob/v12.x/CONFIG.md
and https://github.com/limine-bootloader/limine/blob/v12.x/USAGE.md.
dracut module interface:
https://github.com/dracut-ng/dracut-ng/blob/main/man/dracut.modules.7.adoc.
QEMU block device and backing-file options:
https://www.qemu.org/docs/master/system/invocation.html.
.SH SEE ALSO
.BR holy-init (8),
.BR holypkg (8)