#!/bin/bash # Common functions and utilities for Zero OS Alpine Initramfs Builder # Strict error handling set -euo pipefail # Script directory detection (only if not already set) if [[ -z "${SCRIPT_DIR:-}" ]]; then SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" fi if [[ -z "${PROJECT_ROOT:-}" ]]; then PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" fi # Colors for output (if terminal supports it) if [[ -t 1 ]]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color else RED='' GREEN='' YELLOW='' BLUE='' NC='' fi # Logging functions function log_info() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${GREEN}[INFO]${NC} ${timestamp} - $*" >&2 } function log_warn() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${YELLOW}[WARN]${NC} ${timestamp} - $*" >&2 } function log_error() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${RED}[ERROR]${NC} ${timestamp} - $*" >&2 } function log_debug() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') if [[ "${DEBUG:-0}" == "1" ]]; then echo -e "${BLUE}[DEBUG]${NC} ${timestamp} - $*" >&2 fi } # Command execution with full transparency function safe_execute() { # Stream output live when DEBUG=1 or inside container; otherwise capture and emit only on error. local cmd_display="$*" log_info "Executing: ${cmd_display}" if [[ "${DEBUG:-0}" == "1" ]] || in_container; then if ! "$@"; then log_error "Command failed: ${cmd_display}" exit 1 fi else local output if ! output=$("$@" 2>&1); then log_error "Command failed: ${cmd_display}" log_error "Output: ${output}" exit 1 else log_debug "Command completed successfully: ${cmd_display}" fi fi } # Always-streaming variant (forces live stdout/stderr regardless of DEBUG) function safe_execute_stream() { local cmd_display="$*" log_info "Executing (stream): ${cmd_display}" if ! "$@"; then log_error "Command failed: ${cmd_display}" exit 1 fi } # Section headers with clear text separators function section_header() { local title="$1" echo "" echo "==================================================" echo "SECTION: ${title}" echo "==================================================" log_info "Starting section: ${title}" } # Check if command exists function command_exists() { command -v "$1" >/dev/null 2>&1 } # Check if we're running in a container function in_container() { [[ -f /.dockerenv ]] || [[ -f /run/.containerenv ]] || grep -q 'container' /proc/1/cgroup 2>/dev/null } # Verify required tools are available function check_dependencies() { local missing_deps=() # Core build tools local required_tools=( "git" "wget" "tar" "gzip" "xz" "cpio" "strip" "upx" "rustc" "cargo" ) for tool in "${required_tools[@]}"; do if ! command_exists "$tool"; then missing_deps+=("$tool") fi done # Check for container runtime (if not in container) if ! in_container; then if ! command_exists "podman" && ! command_exists "docker"; then missing_deps+=("podman or docker") fi fi if [[ ${#missing_deps[@]} -gt 0 ]]; then log_error "Missing required dependencies:" for dep in "${missing_deps[@]}"; do log_error " - $dep" done return 1 fi log_info "All dependencies satisfied" return 0 } # Create directory safely function safe_mkdir() { local dir="$1" log_debug "Creating directory: ${dir}" safe_execute mkdir -p "$dir" } # Remove directory safely function safe_rmdir() { local dir="$1" if [[ -d "$dir" ]]; then log_debug "Removing directory: ${dir}" safe_execute rm -rf "$dir" fi } # Copy file/directory safely function safe_copy() { local src="$1" local dst="$2" log_debug "Copying: ${src} -> ${dst}" safe_execute cp -r "$src" "$dst" } # Check if path is absolute function is_absolute_path() { [[ "$1" = /* ]] } # Resolve relative path to absolute function resolve_path() { local path="$1" if is_absolute_path "$path"; then echo "$path" else echo "$(pwd)/$path" fi } # Get file size in human readable format function get_file_size() { local file="$1" if [[ -f "$file" ]]; then du -h "$file" | cut -f1 else echo "0B" fi } # Get short git commit hash from a git repository directory function get_git_commit_hash() { local repo_dir="$1" local short="${2:-true}" # Default to short hash if [[ ! -d "$repo_dir/.git" ]]; then echo "unknown" return 1 fi local hash if [[ "$short" == "true" ]]; then hash=$(cd "$repo_dir" && git rev-parse --short HEAD 2>/dev/null || echo "unknown") else hash=$(cd "$repo_dir" && git rev-parse HEAD 2>/dev/null || echo "unknown") fi echo "$hash" } # Wait for file to exist with timeout function wait_for_file() { local file="$1" local timeout="${2:-30}" local count=0 while [[ ! -f "$file" && $count -lt $timeout ]]; do sleep 1 ((count++)) done [[ -f "$file" ]] } # Cleanup function for traps function cleanup_on_exit() { local exit_code=$? log_info "Build process exiting with code: ${exit_code}" # Unmount any mounted filesystems if [[ -n "${CLEANUP_MOUNTS:-}" ]]; then for mount in $CLEANUP_MOUNTS; do if mountpoint -q "$mount" 2>/dev/null; then log_info "Unmounting: $mount" umount "$mount" 2>/dev/null || true fi done fi exit $exit_code } # Set up exit trap trap cleanup_on_exit EXIT INT TERM # Load build configuration after functions are defined BUILD_CONF="${PROJECT_ROOT}/config/build.conf" if [[ -f "$BUILD_CONF" ]]; then log_debug "Loading build configuration from: ${BUILD_CONF}" # shellcheck source=/dev/null source "$BUILD_CONF" else log_warn "Build configuration not found: ${BUILD_CONF}" log_warn "Using default values" fi # Normalize key directory variables to absolute paths anchored at PROJECT_ROOT. # This prevents later re-sourcing from accidentally re-introducing relative paths. if [[ -z "${INSTALL_DIR:-}" ]]; then INSTALL_DIR="${PROJECT_ROOT}/initramfs" elif [[ "${INSTALL_DIR}" != /* ]]; then INSTALL_DIR="${PROJECT_ROOT}/${INSTALL_DIR#./}" fi if [[ -z "${COMPONENTS_DIR:-}" ]]; then COMPONENTS_DIR="${PROJECT_ROOT}/components" elif [[ "${COMPONENTS_DIR}" != /* ]]; then COMPONENTS_DIR="${PROJECT_ROOT}/${COMPONENTS_DIR#./}" fi if [[ -z "${KERNEL_DIR:-}" ]]; then KERNEL_DIR="${PROJECT_ROOT}/kernel" elif [[ "${KERNEL_DIR}" != /* ]]; then KERNEL_DIR="${PROJECT_ROOT}/${KERNEL_DIR#./}" fi if [[ -z "${DIST_DIR:-}" ]]; then DIST_DIR="${PROJECT_ROOT}/dist" elif [[ "${DIST_DIR}" != /* ]]; then DIST_DIR="${PROJECT_ROOT}/${DIST_DIR#./}" fi # Export common variables export SCRIPT_DIR PROJECT_ROOT export INSTALL_DIR COMPONENTS_DIR KERNEL_DIR DIST_DIR export -f log_info log_warn log_error log_debug export -f safe_execute safe_execute_stream section_header export -f command_exists in_container check_dependencies export -f safe_mkdir safe_rmdir safe_copy export -f is_absolute_path resolve_path get_file_size wait_for_file