Files
zosbuilder/scripts/lib/alpine.sh
Jan De Landtsheer 8c3868b242 ix init script duplication and CPIO creation issues
- Remove duplicate /sbin/init copying from initramfs_setup_zinit()
- Only /init should be config/init (initramfs setup script)
- No /sbin/init needed - config/init calls 'switch_root /mnt/root /sbin/zinit init'
- Remove unsupported cpio --owner option that broke CPIO creation
- Fix validation to not expect /sbin/init file
- Correct boot flow: /init → switch_root → /sbin/zinit init
- Remove strip and UPX compression from zinit binary copying
- UPX compression was corrupting the zinit binary causing segfaults after switch_root
- Keep zinit unmodified as it's
2025-09-05 11:43:25 +02:00

468 lines
16 KiB
Bash

#!/bin/bash
# Alpine miniroot and package operations
# Source common functions
LIB_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${LIB_SCRIPT_DIR}/common.sh"
# Alpine configuration
ALPINE_VERSION="${ALPINE_VERSION:-3.22}"
ALPINE_ARCH="${ALPINE_ARCH:-x86_64}"
ALPINE_MIRROR="${ALPINE_MIRROR:-https://dl-cdn.alpinelinux.org/alpine}"
# Extract Alpine miniroot to target directory
function alpine_extract_miniroot() {
local target_dir="$1"
local version="${2:-$ALPINE_VERSION}"
local arch="${3:-$ALPINE_ARCH}"
section_header "Extracting Alpine Miniroot"
local url="${ALPINE_MIRROR}/v${version}/releases/${arch}/alpine-minirootfs-${version}.0-${arch}.tar.gz"
local temp_file="/tmp/alpine-miniroot-${version}-${arch}.tar.gz"
log_info "Alpine version: ${version}"
log_info "Architecture: ${arch}"
log_info "Target directory: ${target_dir}"
# Clean target directory (handle permission issues gracefully)
if [[ -d "$target_dir" ]]; then
log_info "Cleaning existing target directory"
if ! rm -rf "$target_dir" 2>/dev/null; then
log_warn "Could not remove existing directory, trying to clean contents"
rm -rf "$target_dir"/* 2>/dev/null || true
fi
fi
safe_mkdir "$target_dir"
# Download miniroot
log_info "Downloading Alpine miniroot from: ${url}"
safe_execute wget --progress=dot:giga -O "$temp_file" "$url"
# Verify download
if [[ ! -f "$temp_file" ]]; then
log_error "Failed to download Alpine miniroot"
return 1
fi
local file_size=$(get_file_size "$temp_file")
log_info "Downloaded miniroot size: ${file_size}"
# Extract miniroot
log_info "Extracting miniroot to: ${target_dir}"
safe_execute tar -xzf "$temp_file" -C "$target_dir"
# Cleanup download
safe_execute rm "$temp_file"
# Verify extraction
if [[ ! -f "${target_dir}/etc/alpine-release" ]]; then
log_error "Alpine miniroot extraction failed - missing alpine-release"
return 1
fi
local alpine_release=$(cat "${target_dir}/etc/alpine-release")
log_info "Extracted Alpine release: ${alpine_release}"
log_info "Alpine miniroot extraction complete"
}
# Setup chroot environment for package operations
function alpine_setup_chroot() {
local initramfs_dir="$1"
section_header "Setting Up Alpine Chroot Environment"
# Create essential directories
safe_mkdir "${initramfs_dir}/proc"
safe_mkdir "${initramfs_dir}/sys"
safe_mkdir "${initramfs_dir}/dev"
safe_mkdir "${initramfs_dir}/dev/pts"
safe_mkdir "${initramfs_dir}/tmp"
safe_mkdir "${initramfs_dir}/run"
# Mount essential filesystems
log_info "Mounting essential filesystems in chroot"
if ! mountpoint -q "${initramfs_dir}/proc" 2>/dev/null; then
safe_execute mount --bind /proc "${initramfs_dir}/proc"
export CLEANUP_MOUNTS="${CLEANUP_MOUNTS:-} ${initramfs_dir}/proc"
fi
if ! mountpoint -q "${initramfs_dir}/sys" 2>/dev/null; then
safe_execute mount --bind /sys "${initramfs_dir}/sys"
export CLEANUP_MOUNTS="${CLEANUP_MOUNTS:-} ${initramfs_dir}/sys"
fi
if ! mountpoint -q "${initramfs_dir}/dev" 2>/dev/null; then
safe_execute mount --bind /dev "${initramfs_dir}/dev"
export CLEANUP_MOUNTS="${CLEANUP_MOUNTS:-} ${initramfs_dir}/dev"
fi
if ! mountpoint -q "${initramfs_dir}/dev/pts" 2>/dev/null; then
safe_execute mount --bind /dev/pts "${initramfs_dir}/dev/pts"
export CLEANUP_MOUNTS="${CLEANUP_MOUNTS:-} ${initramfs_dir}/dev/pts"
fi
# Setup resolv.conf for package downloads
if [[ -f /etc/resolv.conf ]]; then
safe_copy /etc/resolv.conf "${initramfs_dir}/etc/resolv.conf"
fi
log_info "Chroot environment setup complete"
}
# Cleanup chroot environment
function alpine_cleanup_chroot() {
local initramfs_dir="$1"
section_header "Cleaning Up Alpine Chroot Environment"
# Unmount filesystems in reverse order
local mounts=(
"${initramfs_dir}/dev/pts"
"${initramfs_dir}/dev"
"${initramfs_dir}/sys"
"${initramfs_dir}/proc"
)
for mount in "${mounts[@]}"; do
if mountpoint -q "$mount" 2>/dev/null; then
log_info "Unmounting: $mount"
safe_execute umount "$mount" || log_warn "Failed to unmount $mount"
fi
done
# Clear cleanup list
export CLEANUP_MOUNTS=""
log_info "Chroot cleanup complete"
}
# Install packages from packages.list (NO OpenRC)
function alpine_install_packages() {
local initramfs_dir="$1"
local packages_file="$2"
section_header "Installing Alpine Packages"
if [[ ! -f "$packages_file" ]]; then
log_error "Packages file not found: ${packages_file}"
return 1
fi
# Setup chroot environment
alpine_setup_chroot "$initramfs_dir"
# Update package repositories
log_info "Updating package repositories"
safe_execute chroot "$initramfs_dir" apk update
# Read packages from file (excluding comments and empty lines)
local packages=()
while IFS= read -r line; do
# Skip comments and empty lines
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "${line// }" ]]; then
continue
fi
packages+=("$line")
done < "$packages_file"
if [[ ${#packages[@]} -eq 0 ]]; then
log_warn "No packages found in ${packages_file}"
alpine_cleanup_chroot "$initramfs_dir"
return 0
fi
log_info "Installing ${#packages[@]} packages:"
for pkg in "${packages[@]}"; do
log_info " - $pkg"
done
# Install packages (NO OpenRC - explicitly exclude)
log_info "Installing packages with apk"
safe_execute chroot "$initramfs_dir" apk add --no-cache \
--no-scripts \
--clean-protected \
"${packages[@]}"
# Verify critical packages are installed
local critical_packages=("busybox" "musl" "alpine-baselayout")
for pkg in "${critical_packages[@]}"; do
if ! chroot "$initramfs_dir" apk info | grep -q "^${pkg}"; then
log_error "Critical package missing: ${pkg}"
alpine_cleanup_chroot "$initramfs_dir"
return 1
fi
done
# Ensure no OpenRC packages were installed
local openrc_packages=$(chroot "$initramfs_dir" apk info | grep -E "(openrc|sysvinit|systemd)" || true)
if [[ -n "$openrc_packages" ]]; then
log_warn "OpenRC-related packages detected:"
echo "$openrc_packages"
log_warn "These should be removed for zinit-only operation"
fi
alpine_cleanup_chroot "$initramfs_dir"
log_info "Package installation complete"
}
# Aggressive cleanup to minimize size
function alpine_aggressive_cleanup() {
local initramfs_dir="$1"
section_header "Aggressive Alpine Cleanup"
log_info "Starting cleanup in: ${initramfs_dir}"
# Remove documentation and man pages
log_info "Removing documentation and man pages"
safe_rmdir "${initramfs_dir}/usr/share/doc"
safe_rmdir "${initramfs_dir}/usr/share/man"
safe_rmdir "${initramfs_dir}/usr/share/info"
safe_rmdir "${initramfs_dir}/usr/share/gtk-doc"
# Remove locales except C/POSIX
log_info "Removing locales (keeping C/POSIX only)"
if [[ -d "${initramfs_dir}/usr/share/locale" ]]; then
find "${initramfs_dir}/usr/share/locale" -mindepth 1 -maxdepth 1 -type d \
! -name 'C' ! -name 'POSIX' -exec rm -rf {} + 2>/dev/null || true
fi
# Remove development headers and files
log_info "Removing development files"
safe_rmdir "${initramfs_dir}/usr/include"
safe_rmdir "${initramfs_dir}/usr/lib/pkgconfig"
safe_rmdir "${initramfs_dir}/usr/share/pkgconfig"
safe_rmdir "${initramfs_dir}/lib/pkgconfig"
# Remove static libraries
log_info "Removing static libraries"
find "${initramfs_dir}" -name "*.a" -type f -delete 2>/dev/null || true
# Remove APK cache and database backup
log_info "Removing APK cache and database backup"
safe_rmdir "${initramfs_dir}/var/cache/apk"
safe_rmdir "${initramfs_dir}/lib/apk/db"
find "${initramfs_dir}/var/lib/apk" -name "*.old" -delete 2>/dev/null || true
# Remove kernel source and headers if present
log_info "Removing kernel development files"
safe_rmdir "${initramfs_dir}/usr/src"
safe_rmdir "${initramfs_dir}/lib/modules/*/build"
safe_rmdir "${initramfs_dir}/lib/modules/*/source"
# Remove Python bytecode and cache
log_info "Removing Python cache files"
find "${initramfs_dir}" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
find "${initramfs_dir}" -name "*.pyc" -type f -delete 2>/dev/null || true
find "${initramfs_dir}" -name "*.pyo" -type f -delete 2>/dev/null || true
# Remove test files and examples
log_info "Removing test files and examples"
find "${initramfs_dir}" -path "*/test*" -type d -exec rm -rf {} + 2>/dev/null || true
find "${initramfs_dir}" -path "*/example*" -type d -exec rm -rf {} + 2>/dev/null || true
# Remove unnecessary files from usr/share
log_info "Cleaning usr/share directory"
local unwanted_share_dirs=(
"applications"
"icons"
"pixmaps"
"themes"
"fonts"
"sounds"
"desktop-directories"
"mime"
"glib-2.0/schemas"
)
for dir in "${unwanted_share_dirs[@]}"; do
safe_rmdir "${initramfs_dir}/usr/share/${dir}"
done
# Remove large timezone data (keep only UTC)
log_info "Trimming timezone data"
if [[ -d "${initramfs_dir}/usr/share/zoneinfo" ]]; then
find "${initramfs_dir}/usr/share/zoneinfo" -type f ! -name "UTC" ! -path "*/posix/*" -delete 2>/dev/null || true
fi
# Remove empty directories (but preserve essential system directories)
log_info "Removing empty directories (preserving essential system dirs)"
find "${initramfs_dir}" -type d -empty -not -path "*/dev" -not -path "*/proc" -not -path "*/sys" -not -path "*/tmp" -not -path "*/run" -not -path "*/mnt" -not -path "*/home" -not -path "*/root" -not -path "*/opt" -not -path "*/srv" -not -path "*/media*" -delete 2>/dev/null || true
# Calculate size after cleanup
local total_size=$(du -sh "${initramfs_dir}" 2>/dev/null | cut -f1 || echo "unknown")
log_info "Initramfs size after cleanup: ${total_size}"
log_info "Aggressive cleanup complete"
}
# Configure Alpine repositories
function alpine_configure_repos() {
local initramfs_dir="$1"
local version="${2:-$ALPINE_VERSION}"
section_header "Configuring Alpine Repositories"
local repos_file="${initramfs_dir}/etc/apk/repositories"
# Create repositories file
cat > "$repos_file" << EOF
${ALPINE_MIRROR}/v${version}/main
${ALPINE_MIRROR}/v${version}/community
EOF
log_info "Configured Alpine repositories for version ${version}"
}
# Set Alpine system settings
function alpine_configure_system() {
local initramfs_dir="$1"
section_header "Configuring Alpine System Settings"
# Ensure all essential Linux directories exist
log_info "Creating essential Linux filesystem directories"
local essential_dirs=(
"dev" "proc" "sys" "tmp" "run"
"mnt" "home" "root" "opt" "srv" "media"
"media/cdrom" "media/floppy" "media/usb"
"mnt/cdrom" "mnt/floppy" "mnt/usb"
)
for dir in "${essential_dirs[@]}"; do
safe_mkdir "${initramfs_dir}/${dir}"
done
# Set hostname
echo "zero-os" > "${initramfs_dir}/etc/hostname"
# Configure hosts file
cat > "${initramfs_dir}/etc/hosts" << 'EOF'
127.0.0.1 localhost localhost.localdomain
::1 localhost localhost.localdomain
EOF
# Set timezone to UTC
if [[ -f "${initramfs_dir}/usr/share/zoneinfo/UTC" ]]; then
safe_execute ln -sf /usr/share/zoneinfo/UTC "${initramfs_dir}/etc/localtime"
fi
# Configure minimal profile
cat > "${initramfs_dir}/etc/profile" << 'EOF'
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export PS1='\h:\w\$ '
export HOME=/root
export TERM=xterm
umask 022
EOF
# Set root shell (if chsh is available)
if chroot "$initramfs_dir" which chsh >/dev/null 2>&1; then
safe_execute chroot "$initramfs_dir" chsh -s /bin/sh root
else
log_info "chsh not available in minimal Alpine, skipping shell change"
log_info "Root shell defaults to /bin/sh in Alpine"
fi
log_info "Alpine system configuration complete"
}
# Install firmware packages for hardware support (intelligent selection)
function alpine_install_firmware() {
local initramfs_dir="$1"
local firmware_conf="$2"
section_header "Installing Required Firmware Packages (Selective)"
# Use smart firmware selection from module analysis if available
local firmware_packages=()
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
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
if [[ ${#firmware_packages[@]} -eq 0 ]]; then
log_warn "No firmware packages to install"
return 0
fi
# Setup chroot environment
alpine_setup_chroot "$initramfs_dir"
log_info "Installing ${#firmware_packages[@]} firmware packages"
# Install firmware packages (allow failures for missing packages)
local failed_packages=()
for package in "${firmware_packages[@]}"; do
if chroot "$initramfs_dir" apk add --no-cache "$package" 2>/dev/null; then
log_info "✓ Installed firmware: $package"
else
log_warn "✗ Failed to install firmware: $package (may not be available)"
failed_packages+=("$package")
fi
done
# Report installation results
local installed_count=$((${#firmware_packages[@]} - ${#failed_packages[@]}))
log_info "Firmware installation: ${installed_count} installed, ${#failed_packages[@]} failed"
# List installed firmware files
log_info "Checking installed firmware files:"
local firmware_count=0
if [[ -d "${initramfs_dir}/lib/firmware" ]]; then
firmware_count=$(find "${initramfs_dir}/lib/firmware" -type f | wc -l)
local firmware_size=$(du -sh "${initramfs_dir}/lib/firmware" 2>/dev/null | cut -f1 || echo "0B")
log_info " Firmware files: ${firmware_count} (${firmware_size})"
# Log some example firmware files for verification (avoid SIGPIPE)
log_debug "Sample firmware files:"
if [[ $firmware_count -gt 0 ]]; then
find "${initramfs_dir}/lib/firmware" -type f -print0 | head -z -n 10 | while IFS= read -r -d '' fw; do
log_debug " $(basename "$fw")"
done 2>/dev/null || true
fi
else
log_warn "No firmware directory found after installation"
fi
alpine_cleanup_chroot "$initramfs_dir"
log_info "Smart firmware installation complete: ${firmware_count} files"
}
# Export functions
export -f alpine_extract_miniroot alpine_setup_chroot alpine_cleanup_chroot
export -f alpine_install_packages alpine_install_firmware alpine_aggressive_cleanup
export -f alpine_configure_repos alpine_configure_system