feat: Implement complete Zero OS Alpine Initramfs Builder
- Complete bash framework with strict error handling - Modular library system (docker, alpine, components, initramfs, kernel, testing) - Rust component integration (zinit, rfs, mycelium) with musl targeting - Rootless Docker/Podman support for GitHub Actions - Centralized configuration in config/build.conf - 2-stage module loading system - Strip + UPX optimization for minimal size - Complete zinit integration replacing OpenRC - GitHub Actions CI/CD pipeline - Comprehensive documentation and usage guides Components: - Latest stable kernel 6.12.44 - Alpine Linux 3.22 base - ThreeFold components: zinit, mycelium, rfs, corex - Target: ~8-12MB final initramfs.cpio.xz
This commit is contained in:
354
scripts/lib/alpine.sh
Normal file
354
scripts/lib/alpine.sh
Normal file
@@ -0,0 +1,354 @@
|
||||
#!/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
|
||||
if [[ -d "$target_dir" ]]; then
|
||||
log_info "Cleaning existing target directory"
|
||||
safe_rmdir "$target_dir"
|
||||
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
|
||||
log_info "Removing empty directories"
|
||||
find "${initramfs_dir}" -type d -empty -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"
|
||||
|
||||
# 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
|
||||
safe_execute chroot "$initramfs_dir" chsh -s /bin/sh root
|
||||
|
||||
log_info "Alpine system configuration complete"
|
||||
}
|
||||
|
||||
# Export functions
|
||||
export -f alpine_extract_miniroot alpine_setup_chroot alpine_cleanup_chroot
|
||||
export -f alpine_install_packages alpine_aggressive_cleanup
|
||||
export -f alpine_configure_repos alpine_configure_system
|
||||
Reference in New Issue
Block a user