initramfs+modules: robust copy aliasing, curated stage1 + PHYs, firmware policy via firmware.conf, runtime readiness, build ID; docs sync

Summary of changes (with references):\n\nModules + PHY coverage\n- Curated and normalized stage1 list in [config.modules.conf](config/modules.conf:1):\n  - Boot-critical storage, core virtio, common NICs (Intel/Realtek/Broadcom), overlay/fuse, USB HCD/HID.\n  - Added PHY drivers required by NIC MACs:\n    * realtek (for r8169, etc.)\n    * broadcom families: broadcom, bcm7xxx, bcm87xx, bcm_phy_lib, bcm_phy_ptp\n- Robust underscore↔hyphen aliasing during copy so e.g. xhci_pci → xhci-pci.ko, hid_generic → hid-generic.ko:\n  - [bash.initramfs_copy_resolved_modules()](scripts/lib/initramfs.sh:990)\n\nFirmware policy and coverage\n- Firmware selection now authoritative via [config/firmware.conf](config/firmware.conf:1); ignore modules.conf firmware hints:\n  - [bash.initramfs_setup_modules()](scripts/lib/initramfs.sh:229)\n  - Count from firmware.conf for reporting; remove stale required-firmware.list.\n- Expanded NIC firmware set (bnx2, bnx2x, tigon, intel, realtek, rtl_nic, qlogic, e100) in [config.firmware.conf](config/firmware.conf:1).\n- Installer enforces firmware.conf source-of-truth in [bash.alpine_install_firmware()](scripts/lib/alpine.sh:392).\n\nEarly input & build freshness\n- Write a runtime build stamp to /etc/zero-os-build-id for embedded initramfs verification:\n  - [bash.initramfs_finalize_customization()](scripts/lib/initramfs.sh:568)\n- Minor init refinements in [config.init](config/init:1) (ensures /home, consistent depmod path).\n\nRebuild helper improvements\n- [scripts/rebuild-after-zinit.sh](scripts/rebuild-after-zinit.sh:1):\n  - Added --verify-only; container-aware execution; selective marker clears only.\n  - Prints stage status before/after; avoids --rebuild-from; resolves full kernel version for diagnostics.\n\nRemote flist readiness + zinit\n- Init scripts now probe BASE_URL readiness and accept FLISTS_BASE_URL/FLIST_BASE_URL; firmware target is /lib/firmware:\n  - [sh.firmware.sh](config/zinit/init/firmware.sh:1)\n  - [sh.modules.sh](config/zinit/init/modules.sh:1)\n\nContainer, docs, and utilities\n- Stream container build logs by calling runtime build directly in [bash.docker_build_container()](scripts/lib/docker.sh:56).\n- Docs updated to reflect firmware policy, runtime readiness, rebuild helper, early input, and GRUB USB:\n  - [docs.NOTES.md](docs/NOTES.md)\n  - [docs.PROMPT.md](docs/PROMPT.md)\n  - [docs.review-rfs-integration.md](docs/review-rfs-integration.md)\n- Added GRUB USB creator (referenced in docs): [scripts/make-grub-usb.sh](scripts/make-grub-usb.sh)\n\nCleanup\n- Removed legacy/duplicated config trees under configs/ and config/zinit.old/.\n- Minor newline and ignore fixes: [.gitignore](.gitignore:1)\n\nNet effect\n- Runtime now has correct USB HCDs/HID-generic and NIC+PHY coverage (Realtek/Broadcom), with matching firmware installed in initramfs.\n- Rebuild workflow is minimal and host/container-aware; docs are aligned with implemented behavior.\n
This commit is contained in:
2025-09-23 14:03:01 +02:00
parent 2fba2bd4cd
commit ad0a06e267
87 changed files with 833 additions and 17307 deletions

View File

@@ -395,43 +395,35 @@ function alpine_install_firmware() {
section_header "Installing Required Firmware Packages (Selective)"
# Use smart firmware selection from module analysis if available
# Policy: firmware.conf is the authoritative source for initramfs firmware
local firmware_packages=()
if [[ ! -f "$firmware_conf" ]]; then
log_warn "Firmware configuration not found: ${firmware_conf}; skipping firmware installation to initramfs"
return 0
fi
# Ignore any REQUIRED_FIRMWARE_PACKAGES hints to avoid duplication/mismatch with modules.conf
if [[ -n "${REQUIRED_FIRMWARE_PACKAGES:-}" ]]; then
log_info "Using intelligent firmware selection based on COPIED modules only"
read -ra firmware_packages <<< "$REQUIRED_FIRMWARE_PACKAGES"
log_info "Required firmware packages (${#firmware_packages[@]}):"
for package in "${firmware_packages[@]}"; do
log_info "${package}"
done
else
log_info "Falling back to firmware configuration file"
if [[ ! -f "$firmware_conf" ]]; then
log_warn "No firmware configuration found and no module requirements"
log_info "Skipping firmware installation"
return 0
log_info "Firmware selection: ignoring REQUIRED_FIRMWARE_PACKAGES; using ${firmware_conf} as authoritative source"
fi
# Read firmware packages from config (excluding comments and empty lines)
while IFS=: read -r package description; do
# Skip comments and empty lines
if [[ "$package" =~ ^[[:space:]]*# ]] || [[ -z "${package// }" ]]; then
continue
fi
# Read firmware packages from config (excluding comments and empty lines)
while IFS=: read -r package description; do
# Skip comments and empty lines
if [[ "$package" =~ ^[[:space:]]*# ]] || [[ -z "${package// }" ]]; then
continue
fi
# Trim whitespace
package=$(echo "$package" | xargs)
description=$(echo "$description" | xargs)
if [[ -n "$package" ]]; then
firmware_packages+=("$package")
log_info " - ${package}: ${description}"
fi
done < "$firmware_conf"
fi
# Trim whitespace
package=$(echo "$package" | xargs)
description=$(echo "$description" | xargs)
if [[ -n "$package" ]]; then
firmware_packages+=("$package")
log_info " - ${package}: ${description}"
fi
done < "$firmware_conf"
if [[ ${#firmware_packages[@]} -eq 0 ]]; then
log_warn "No firmware packages to install"

View File

@@ -56,7 +56,7 @@ function docker_build_container() {
fi
log_info "Building container image: ${tag}"
safe_execute ${CONTAINER_RUNTIME} build -t "${tag}" -f "${dockerfile_path}" "${PROJECT_ROOT}"
${CONTAINER_RUNTIME} build -t "${tag}" -f "${dockerfile_path}" "${PROJECT_ROOT}"
log_info "Container image built successfully: ${tag}"
}

View File

@@ -250,16 +250,9 @@ function initramfs_setup_modules() {
local stage1_modules=()
local stage1_firmware=()
while IFS=: read -r stage module firmware_line; do
while IFS=: read -r stage module _ignored_fw; do
if [[ "$stage" == "stage1" && -n "$module" ]]; then
stage1_modules+=("$module")
# Extract firmware package name (before any comment)
local firmware=$(echo "$firmware_line" | sed 's/[[:space:]]*#.*//' | tr -d ' ')
if [[ -n "$firmware" && "$firmware" != "none" ]]; then
stage1_firmware+=("$firmware")
required_firmware+=("$firmware")
log_debug "Module $module requires firmware: $firmware"
fi
fi
done < <(grep "^stage1:" "$modules_conf")
@@ -286,32 +279,32 @@ function initramfs_setup_modules() {
local stage2_with_deps=()
# Create firmware requirements list (remove duplicates)
local unique_firmware=($(printf '%s\n' "${required_firmware[@]}" | sort -u))
if [[ ${#unique_firmware[@]} -gt 0 ]]; then
printf '%s\n' "${unique_firmware[@]}" > "${modules_dir}/required-firmware.list"
log_info "Created firmware requirements list: ${#unique_firmware[@]} packages"
for fw in "${unique_firmware[@]}"; do
log_info " Required firmware: $fw"
done
# Export for use by firmware installation
export REQUIRED_FIRMWARE_PACKAGES="${unique_firmware[*]}"
# Firmware selection policy: use config/firmware.conf as the authoritative source.
# Ignore firmware hints in modules.conf (third column) to avoid duplication/mismatch.
if [[ -f "${PROJECT_ROOT}/config/firmware.conf" ]]; then
log_info "Firmware selection: using config/firmware.conf (ignoring modules.conf firmware hints)"
else
log_info "No firmware packages required"
export REQUIRED_FIRMWARE_PACKAGES=""
log_warn "Firmware selection: config/firmware.conf not found; no firmware will be installed into initramfs"
fi
# Remove any stale list created by previous versions
rm -f "${modules_dir}/required-firmware.list" 2>/dev/null || true
# Do NOT export REQUIRED_FIRMWARE_PACKAGES anymore; alpine_install_firmware reads the config file directly.
# Create module loading scripts
initramfs_create_module_scripts "$initramfs_dir" "$kernel_version"
# Report final counts
local stage1_count=${#stage1_with_deps[@]}
local firmware_count=${#unique_firmware[@]}
# Count firmware entries from authoritative firmware.conf (non-empty, non-comment lines)
local firmware_count=0
if [[ -f "${PROJECT_ROOT}/config/firmware.conf" ]]; then
firmware_count=$(grep -v '^[[:space:]]*#' "${PROJECT_ROOT}/config/firmware.conf" | sed '/^[[:space:]]*$/d' | wc -l | tr -d ' ')
fi
log_info "Module configuration complete:"
log_info " Stage1 (critical + deps): ${stage1_count} modules"
log_info " Stage2: disabled (only using stage1)"
log_info " Required firmware packages: ${firmware_count}"
log_info " Firmware packages (from firmware.conf): ${firmware_count}"
log_info " Total modules: ${stage1_count}"
}
@@ -631,6 +624,17 @@ EOF
log_info "Branding disabled: leaving /etc/motd, /etc/issue and root password unchanged"
fi
# Write a unique build stamp to verify runtime image freshness
# Allows confirming that the booted VM uses the latest embedded initramfs
local _rand=""
if command_exists "openssl"; then _rand=$(openssl rand -hex 6 2>/dev/null || true); fi
local _build_id="ZOS-BUILD $(date -u +%Y-%m-%dT%H:%M:%SZ) ${_rand}"
# Write via tee to ensure creation + visible logging, avoid shell redirection edge cases
safe_mkdir "${initramfs_dir}/etc"
printf "%s\n" "${_build_id}" | tee "${initramfs_dir}/etc/zero-os-build-id" >/dev/null
safe_execute chmod 644 "${initramfs_dir}/etc/zero-os-build-id"
log_info "Build ID: ${_build_id} (written to /etc/zero-os-build-id)"
# Ensure essential Linux filesystem directories exist (defensive), including /home
# Some earlier stages or cleanups may have run in previous builds; enforce presence now.
log_info "Ensuring essential directories exist (including /home)"
@@ -1069,25 +1073,56 @@ function initramfs_copy_resolved_modules() {
for module in "${all_modules[@]}"; do
local module_found=false
# Try to find module file
local module_file=$(find "$container_modules_path" -name "${module}.ko*" -type f | head -1)
# Try to find module file with robust underscore/hyphen aliasing
# modprobe treats '_' and '-' as equivalent, but our file search must do both.
local query="${module}"
local module_file=""
# 1) Exact match (as given)
module_file=$(find "$container_modules_path" -name "${query}.ko*" -type f | head -1)
# 2) Fallback: underscores -> hyphens (e.g., xhci_pci -> xhci-pci)
if [[ -z "$module_file" || ! -f "$module_file" ]]; then
local alt="${query//_/-}"
if [[ "$alt" != "$query" ]]; then
module_file=$(find "$container_modules_path" -name "${alt}.ko*" -type f | head -1)
if [[ -n "$module_file" && -f "$module_file" ]]; then
log_info "Resolved module alias: ${query} -> ${alt} (hyphen)"
query="$alt"
fi
fi
fi
# 3) Fallback: hyphens -> underscores (defensive; modinfo sometimes reports hyphen)
if [[ -z "$module_file" || ! -f "$module_file" ]]; then
local alt2="${query//-/_}"
if [[ "$alt2" != "$query" ]]; then
local mf2
mf2=$(find "$container_modules_path" -name "${alt2}.ko*" -type f | head -1)
if [[ -n "$mf2" && -f "$mf2" ]]; then
log_info "Resolved module alias: ${query} -> ${alt2} (underscore)"
query="$alt2"
module_file="$mf2"
fi
fi
fi
if [[ -n "$module_file" && -f "$module_file" ]]; then
# Preserve directory structure
local rel_path="${module_file#${container_modules_path}/}"
local target_dir="${initramfs_modules_dir}/$(dirname "$rel_path")"
safe_mkdir "$target_dir"
safe_execute cp "$module_file" "${initramfs_modules_dir}/${rel_path}"
log_debug "Copied module: $module from $rel_path"
log_debug "Copied module: ${query} from ${rel_path}"
((copied_count++))
module_found=true
fi
if [[ "$module_found" == "false" ]]; then
log_warn "Module not found in container: $module"
log_warn "Module not found in container (after alias fallbacks): ${module}"
((failed_count++))
fi
done

256
scripts/make-grub-usb.sh Normal file
View File

@@ -0,0 +1,256 @@
#!/usr/bin/env bash
# Create a GRUB USB disk that boots on both BIOS (legacy) and UEFI.
# It copies kernel (vmlinuz) and initramfs, installs GRUB for i386-pc and x86_64-efi,
# and writes a minimal grub.cfg with optional kernel parameters.
#
# DANGEROUS: This script will repartition and format the target device (e.g., /dev/sdX).
#
# Usage:
# sudo scripts/make-grub-usb.sh /dev/sdX \
# --kernel dist/vmlinuz.efi \
# --initrd dist/initramfs.cpio.xz \
# --kparams "console=ttyS0 initdebug=true" \
# --label ZOSBOOT \
# --no-confirm
#
# Defaults (resolved relative to repo root):
# --kernel dist/vmlinuz.efi
# --initrd dist/initramfs.cpio.xz
# --label ZOS
# --kparams "" (none)
#
# Requirements on host:
# - grub-install (supports --target=i386-pc and --target=x86_64-efi)
# - parted, sfdisk (optional), dosfstools (mkfs.vfat), util-linux (lsblk, partprobe)
# - run as root
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
DEVICE="${1:-}"
shift || true
KERNEL="${PROJECT_ROOT}/dist/vmlinuz.efi"
# Default: no separate initrd because initramfs is embedded in kernel. Enable with --with-initrd or --initrd.
INITRD=""
KPARAMS=""
LABEL="ZOS"
NO_CONFIRM=0
ESP_SIZE_MB=512
# Request including separate initrd
WANT_INITRD=0
error() { echo "[ERROR] $*" >&2; }
info() { echo "[INFO] $*"; }
warn() { echo "[WARN] $*"; }
die() { error "$*"; exit 1; }
require_root() {
[[ "$EUID" -eq 0 ]] || die "This script must be run as root."
}
command_exists() { command -v "$1" &>/dev/null; }
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--kernel)
KERNEL="$(realpath -m "${2:-}")"; shift 2 ;;
--initrd)
INITRD="$(realpath -m "${2:-}")"; shift 2 ;;
--with-initrd)
WANT_INITRD=1; shift ;;
--kparams)
KPARAMS="${2:-}"; shift 2 ;;
--label)
LABEL="${2:-ZOS}"; shift 2 ;;
--no-confirm)
NO_CONFIRM=1; shift ;;
--esp-size-mb)
ESP_SIZE_MB="${2:-512}"; shift 2 ;;
-*)
die "Unknown option: $1" ;;
*)
die "Unexpected argument: $1" ;;
esac
done
}
confirm_dangerous() {
info "About to wipe and repartition device: ${DEVICE}"
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT "${DEVICE}"
if [[ $NO_CONFIRM -eq 1 ]]; then
info "--no-confirm provided; proceeding without interactive confirmation"
return
fi
echo
read -r -p "Type the DEVICE path to CONFIRM (e.g., ${DEVICE}) or 'abort' to cancel: " ans
[[ "$ans" == "$DEVICE" ]] || die "Confirmation mismatch or aborted."
}
check_prereqs() {
command_exists parted || die "parted not found"
command_exists mkfs.vfat || die "mkfs.vfat (dosfstools) not found"
command_exists grub-install || die "grub-install not found"
command_exists lsblk || die "lsblk not found"
command_exists partprobe || die "partprobe not found"
}
resolve_defaults() {
[[ -b "$DEVICE" ]] || die "Device not found or not a block device: ${DEVICE}"
[[ -f "$KERNEL" ]] || die "Kernel file not found: ${KERNEL}"
if [[ $WANT_INITRD -eq 1 ]]; then
# If user asked for separate initrd but none specified, use default path
if [[ -z "${INITRD}" ]]; then
INITRD="${PROJECT_ROOT}/dist/initramfs.cpio.xz"
fi
[[ -f "$INITRD" ]] || die "Requested --with-initrd but initramfs not found: ${INITRD}"
else
# By default, no separate initrd (initramfs is embedded in kernel)
INITRD=""
fi
}
umount_partitions() {
info "Unmounting any mounted partitions of ${DEVICE}"
local kids
kids="$(lsblk -nr -o NAME "${DEVICE}" | tail -n +2 || true)"
if [[ -n "${kids}" ]]; then
while read -r name; do
[[ -z "$name" ]] && continue
local path="/dev/${name}"
if mountpoint -q -- "/dev/${name}" 2>/dev/null; then
umount -f "/dev/${name}" || true
fi
# also try mounts by path
local mp
mp="$(lsblk -nr -o MOUNTPOINT "$path" || true)"
if [[ -n "$mp" ]]; then
for m in $mp; do
[[ -n "$m" ]] && umount -f "$m" || true
done
fi
done <<< "${kids}"
fi
}
partition_device_gpt() {
info "Creating GPT with BIOS boot + ESP on ${DEVICE}"
# Create a new GPT
parted -s "${DEVICE}" mklabel gpt
# Create BIOS boot partition 1MiB..3MiB
parted -s "${DEVICE}" mkpart biosgrub 1MiB 3MiB
parted -s "${DEVICE}" set 1 bios_grub on
# Create ESP (FAT32) from 3MiB to 3MiB + ESP_SIZE_MB
local esp_end="$((3 + ESP_SIZE_MB))MiB"
parted -s "${DEVICE}" mkpart esp fat32 3MiB "${esp_end}"
parted -s "${DEVICE}" set 2 esp on
# Inform kernel
partprobe "${DEVICE}"
sleep 1
}
format_esp() {
local part="${DEVICE}2"
[[ -b "$part" ]] || die "ESP partition not found: ${part}"
info "Formatting ESP ${part} as FAT32 (label=${LABEL})"
mkfs.vfat -F32 -n "${LABEL}" "$part"
}
mount_esp() {
ESP_MNT="$(mktemp -d)"
info "Mounting ESP at ${ESP_MNT}"
mount "${DEVICE}2" "${ESP_MNT}"
}
install_grub() {
info "Installing GRUB (BIOS i386-pc) to ${DEVICE}"
grub-install --target=i386-pc --boot-directory="${ESP_MNT}/boot" --recheck "${DEVICE}"
info "Installing GRUB (UEFI x86_64-efi) to ESP"
mkdir -p "${ESP_MNT}/EFI/BOOT"
grub-install --target=x86_64-efi --efi-directory="${ESP_MNT}" --boot-directory="${ESP_MNT}/boot" --removable --recheck
}
copy_kernel_initrd() {
mkdir -p "${ESP_MNT}/boot"
info "Copying kernel to ESP /boot/vmlinuz"
cp -f "${KERNEL}" "${ESP_MNT}/boot/vmlinuz"
if [[ -n "${INITRD}" ]]; then
info "Copying initramfs to ESP /boot/initramfs.cpio.xz"
cp -f "${INITRD}" "${ESP_MNT}/boot/initramfs.cpio.xz"
fi
}
write_grub_cfg() {
local cfg="${ESP_MNT}/boot/grub/grub.cfg"
info "Writing GRUB config: ${cfg}"
mkdir -p "$(dirname "$cfg")"
cat > "$cfg" <<'EOF'
set default=0
set timeout=3
if [ "${grub_platform}" = "efi" ]; then
set gfxpayload=keep
fi
menuentry "Zero-OS" {
linux /boot/vmlinuz __KPARAMS__
__INITRD_LINE__
}
EOF
local initrd_line=""
if [[ -n "${INITRD}" ]]; then
initrd_line=" initrd /boot/initramfs.cpio.xz"
fi
# Substitute placeholders
sed -i \
-e "s|__KPARAMS__|${KPARAMS}|g" \
-e "s|__INITRD_LINE__|${initrd_line}|g" \
"$cfg"
# For removable media on UEFI, ensure fallback bootloader path exists
if [[ -f "/usr/lib/grub/x86_64-efi/monolithic/grubx64.efi" ]]; then
cp -f "/usr/lib/grub/x86_64-efi/monolithic/grubx64.efi" "${ESP_MNT}/EFI/BOOT/BOOTX64.EFI" || true
elif [[ -f "${ESP_MNT}/EFI/BOOT/grubx64.efi" ]]; then
cp -f "${ESP_MNT}/EFI/BOOT/grubx64.efi" "${ESP_MNT}/EFI/BOOT/BOOTX64.EFI" || true
fi
}
cleanup() {
set +e
if mountpoint -q "${ESP_MNT}" 2>/dev/null; then
info "Unmounting ESP ${ESP_MNT}"
umount "${ESP_MNT}"
fi
[[ -n "${ESP_MNT:-}" ]] && rmdir "${ESP_MNT}" 2>/dev/null || true
}
main() {
require_root
[[ -n "${DEVICE}" ]] || die "Usage: $0 /dev/sdX [--kernel path] [--initrd path] [--kparams \"...\"] [--label ZOS] [--no-confirm]"
parse_args "$@"
resolve_defaults
check_prereqs
confirm_dangerous
trap cleanup EXIT INT TERM
umount_partitions
partition_device_gpt
format_esp
mount_esp
install_grub
copy_kernel_initrd
write_grub_cfg
info "Done. You can now boot from the USB on BIOS and UEFI systems."
}
main "$@"

View File

@@ -5,6 +5,7 @@
# scripts/rebuild-after-zinit.sh --run-tests # include boot tests (still no kernel rebuild by default)
# scripts/rebuild-after-zinit.sh --with-kernel # also rebuild kernel (re-embed updated initramfs)
# scripts/rebuild-after-zinit.sh --refresh-container-mods # rebuild container /lib/modules if missing (kernel modules stage)
# scripts/rebuild-after-zinit.sh --verify-only # only report detected changes, do not rebuild
# scripts/rebuild-after-zinit.sh -- ... # pass extra args to build.sh
set -euo pipefail
@@ -19,6 +20,7 @@ run_tests=0
extra_args=()
rebuild_kernel=0
refresh_container_mods=0
verify_only=0
# Parse flags; pass through any remaining args to build.sh after --
while [[ $# -gt 0 ]]; do
@@ -35,6 +37,10 @@ while [[ $# -gt 0 ]]; do
refresh_container_mods=1
shift
;;
--verify-only)
verify_only=1
shift
;;
--)
shift
extra_args=("$@")
@@ -51,6 +57,108 @@ done
log "PROJECT_ROOT=${PROJECT_ROOT}"
log "STAGES_DIR=${STAGES_DIR}"
# Show current stage status before any changes (host-safe; does not require container)
log "Stage status (before):"
("${PROJECT_ROOT}/scripts/build.sh" --show-stages) || true
# ------------------------------------------------------------
# Container detection helper
# ------------------------------------------------------------
in_container() {
[[ -f /.dockerenv ]] || [[ -f /run/.containerenv ]] || grep -q 'container' /proc/1/cgroup 2>/dev/null
}
# ------------------------------------------------------------
# Change detection (verify what changed since last archive build)
# ------------------------------------------------------------
marker_init="${STAGES_DIR}/initramfs_create.done"
marker_time=0
if [[ -f "$marker_init" ]]; then
marker_time=$(stat -c %Y "$marker_init" 2>/dev/null || echo 0)
fi
log "Detecting changes since last initramfs_create marker: ${marker_init:-<none>}"
check_dir_changed() {
local path="$1"
local cutoff="$2"
local count
count=$(find "$path" -type f -printf '%T@ %p\n' 2>/dev/null | awk -v c="$cutoff" '$1 > c {n++} END {print n+0}')
echo "${count:-0}"
}
list_some_changes() {
local path="$1"
local cutoff="$2"
# list up to 5 example files
find "$path" -type f -printf '%T@ %p\n' 2>/dev/null | awk -v c="$cutoff" '$1 > c {print $2}' | head -n 5
}
zinit_dir="${PROJECT_ROOT}/config/zinit"
init_file="${PROJECT_ROOT}/config/init"
modules_conf="${PROJECT_ROOT}/config/modules.conf"
zinit_changed=0
init_changed=0
modules_changed=0
if [[ -d "$zinit_dir" ]]; then
zinit_changed=$(check_dir_changed "$zinit_dir" "$marker_time")
fi
if [[ -f "$init_file" ]]; then
if [[ $(stat -c %Y "$init_file" 2>/dev/null || echo 0) -gt $marker_time ]]; then init_changed=1; fi
fi
if [[ -f "$modules_conf" ]]; then
if [[ $(stat -c %Y "$modules_conf" 2>/dev/null || echo 0) -gt $marker_time ]]; then modules_changed=1; fi
fi
log "Changes since last archive:"
log " - config/zinit: ${zinit_changed} file(s) changed"
if [[ "$zinit_changed" -gt 0 ]]; then
list_some_changes "$zinit_dir" "$marker_time" | sed 's/^/ * /' || true
fi
log " - config/init: $([[ $init_changed -eq 1 ]] && echo changed || echo unchanged)"
log " - config/modules.conf: $([[ $modules_changed -eq 1 ]] && echo changed || echo unchanged)"
if [[ "$verify_only" -eq 1 ]]; then
log "verify-only set; exiting without rebuild"
exit 0
fi
# ------------------------------------------------------------
# Container /lib/modules/<FULL_VERSION> presence diagnostics
# (we never clear kernel_modules unless --refresh-container-mods is given)
# ------------------------------------------------------------
compute_full_kver() {
# Read from configs without sourcing (safe in any shell)
local build_conf="${PROJECT_ROOT}/config/build.conf"
local kcfg="${PROJECT_ROOT}/config/kernel.config"
local base_ver=""
local localver=""
if [[ -f "$build_conf" ]]; then
base_ver="$(grep -E '^KERNEL_VERSION=' "$build_conf" | head -1 | cut -d= -f2 | tr -d '\"')"
fi
if [[ -f "$kcfg" ]]; then
localver="$(grep -E '^CONFIG_LOCALVERSION=' "$kcfg" | head -1 | cut -d'\"' -f2)"
fi
echo "${base_ver}${localver}"
}
modules_dir_for_full() {
local full="$1"
echo "/lib/modules/${full}"
}
full_kver="$(compute_full_kver)"
container_modules_dir="$(modules_dir_for_full "$full_kver")"
log "Container modules version: ${full_kver:-<unknown>}"
if [[ -d "$container_modules_dir" ]]; then
before_count=$(find "$container_modules_dir" -type f -name '*.ko*' 2>/dev/null | wc -l | tr -d ' ')
log "Before build: ${container_modules_dir} exists with ${before_count} module file(s)"
else
log "Before build: ${container_modules_dir} not present (fresh container scenario)"
fi
# Minimal set of stages to clear when zinit changes:
# - zinit_setup: recopy zinit YAML and init scripts into initramfs
# - validation: re-check initramfs contents
@@ -87,6 +195,7 @@ if [[ "$rebuild_kernel" -eq 1 ]]; then
fi
# Remove completion markers to force incremental rebuild of those stages
log "Planned markers to clear: ${stages_to_clear[*]}"
for s in "${stages_to_clear[@]}"; do
marker="${STAGES_DIR}/${s}.done"
if [[ -f "$marker" ]]; then
@@ -97,20 +206,39 @@ for s in "${stages_to_clear[@]}"; do
fi
done
# Show stage status after marker removal (still host-safe)
log "Stage status (after marker removal):"
("${PROJECT_ROOT}/scripts/build.sh" --show-stages) || true
# Build
log "Starting incremental rebuild (zinit changes)"
# If we plan to rebuild the kernel, force the pipeline to run from initramfs_create
# so the cpio archive is recreated before kernel_build (ignoring prior .done markers).
# IMPORTANT: Do NOT pass --rebuild-from or --force-rebuild; that would force ALL stages to run.
# We rely exclusively on removed markers to minimally re-run only the necessary stages.
build_from_args=()
if [[ "$rebuild_kernel" -eq 1 ]]; then
build_from_args=(--rebuild-from=initramfs_create)
log "Rebuild-from: initramfs_create (guarantee cpio is recreated before kernel_build)"
fi
if [[ "$run_tests" -eq 1 ]]; then
log "Including boot tests"
DEBUG=1 "${PROJECT_ROOT}/scripts/build.sh" "${build_from_args[@]}" "${extra_args[@]}"
if in_container; then
# Run directly when already inside the dev/build container
if [[ "$run_tests" -eq 1 ]]; then
log "Including boot tests (in-container)"
DEBUG=1 "${PROJECT_ROOT}/scripts/build.sh" "${build_from_args[@]}" "${extra_args[@]}"
else
log "Skipping boot tests (in-container)"
DEBUG=1 "${PROJECT_ROOT}/scripts/build.sh" --skip-tests "${build_from_args[@]}" "${extra_args[@]}"
fi
else
log "Skipping boot tests (use --run-tests to include)"
DEBUG=1 "${PROJECT_ROOT}/scripts/build.sh" --skip-tests "${build_from_args[@]}" "${extra_args[@]}"
# Not in container: delegate to dev-container manager which ensures container exists and is running
devctl="${PROJECT_ROOT}/scripts/dev-container.sh"
if [[ ! -x "$devctl" ]]; then
log "[ERROR] Dev container manager not found: ${devctl}"
log "[HINT] Run ./scripts/build.sh directly (it can start a transient container), or start the dev container via ./scripts/dev-container.sh start"
exit 1
fi
if [[ "$run_tests" -eq 1 ]]; then
log "Including boot tests via dev-container"
"$devctl" build "${build_from_args[@]}" "${extra_args[@]}"
else
log "Skipping boot tests via dev-container"
"$devctl" build --skip-tests "${build_from_args[@]}" "${extra_args[@]}"
fi
fi