diff --git a/.forgejo/workflows/compile-apt-layer.yml b/.forgejo/workflows/compile-apt-layer.yml index 69dd85a..0cb9412 100644 --- a/.forgejo/workflows/compile-apt-layer.yml +++ b/.forgejo/workflows/compile-apt-layer.yml @@ -20,10 +20,10 @@ jobs: steps: - name: Checkout code run: | - echo "Checking out repository..." - git clone https://github.com/${{ github.repository }}.git . - git checkout ${{ github.sha }} - echo "Repository checked out successfully" + echo "Using local repository..." + # Since we're already in the repository, just ensure we're on the right branch + git status + echo "Repository ready for compilation" - name: Set up environment run: | diff --git a/COMPILATION_REPORT.md b/COMPILATION_REPORT.md new file mode 100644 index 0000000..d0ed735 --- /dev/null +++ b/COMPILATION_REPORT.md @@ -0,0 +1,28 @@ +# apt-layer Compilation Report + +**Compilation Date:** Tue Jul 15 00:37:41 PDT 2025 +**Branch:** +**Commit:** + +## File Information + +### apt-layer.sh +- **File:** apt-layer.sh +- **Size:** 276K +- **Lines:** 8887 +- **Executable:** Yes + +### install-apt-layer.sh +- **File:** install-apt-layer.sh +- **Size:** 16K +- **Lines:** 475 +- **Executable:** Yes + +## Validation Results +- **apt-layer.sh Syntax Check:** ✅ Passed +- **apt-layer.sh Help Command:** ✅ Works +- **install-apt-layer.sh Syntax Check:** ✅ Passed +- **install-apt-layer.sh Help Command:** ✅ Works + +## Ready for Distribution +Both compiled scripts are self-contained and ready for use! diff --git a/apt-layer.sh b/apt-layer.sh index cb12d17..fd6efc0 100644 --- a/apt-layer.sh +++ b/apt-layer.sh @@ -6,7 +6,7 @@ # DO NOT modify this file directly as it will be overwritten # # # # apt-layer Tool # -# Generated on: 2025-07-14 14:00:34 # +# Generated on: 2025-07-15 00:37:36 # # # ################################################################################################################ @@ -18,7 +18,7 @@ set -euo pipefail # Inspired by Vanilla OS Apx approach, ParticleOS apt-layer, and rpm-ostree live layering -# Version: 25.07.14 +# Version: 25.07.15 # apt-layer Tool # Enhanced with Container Support and LIVE SYSTEM LAYERING diff --git a/artifacts/COMPILATION_REPORT.md b/artifacts/COMPILATION_REPORT.md new file mode 100644 index 0000000..d0ed735 --- /dev/null +++ b/artifacts/COMPILATION_REPORT.md @@ -0,0 +1,28 @@ +# apt-layer Compilation Report + +**Compilation Date:** Tue Jul 15 00:37:41 PDT 2025 +**Branch:** +**Commit:** + +## File Information + +### apt-layer.sh +- **File:** apt-layer.sh +- **Size:** 276K +- **Lines:** 8887 +- **Executable:** Yes + +### install-apt-layer.sh +- **File:** install-apt-layer.sh +- **Size:** 16K +- **Lines:** 475 +- **Executable:** Yes + +## Validation Results +- **apt-layer.sh Syntax Check:** ✅ Passed +- **apt-layer.sh Help Command:** ✅ Works +- **install-apt-layer.sh Syntax Check:** ✅ Passed +- **install-apt-layer.sh Help Command:** ✅ Works + +## Ready for Distribution +Both compiled scripts are self-contained and ready for use! diff --git a/artifacts/apt-layer.sh b/artifacts/apt-layer.sh new file mode 100644 index 0000000..fd6efc0 --- /dev/null +++ b/artifacts/apt-layer.sh @@ -0,0 +1,8887 @@ +#!/bin/bash + +################################################################################################################ +# # +# WARNING: This file is automatically generated # +# DO NOT modify this file directly as it will be overwritten # +# # +# apt-layer Tool # +# Generated on: 2025-07-15 00:37:36 # +# # +################################################################################################################ + +set -euo pipefail + +# apt-layer Tool - Self-contained version +# This script contains all components merged into a single file +# Enhanced version with container support, multiple package managers, and LIVE SYSTEM LAYERING +# Inspired by Vanilla OS Apx approach, ParticleOS apt-layer, and rpm-ostree live layering + + +# Version: 25.07.15 +# apt-layer Tool +# Enhanced with Container Support and LIVE SYSTEM LAYERING + + +# Fallback logging functions (always defined first) +# Color definitions +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +PURPLE='\033[0;35m' +NC='\033[0m' + +log_info() { + local message="$1" + local script_name="${2:-apt-layer}" + echo -e "${BLUE}[INFO]${NC} [$script_name] $message" +} +log_debug() { + local message="$1" + local script_name="${2:-apt-layer}" + echo -e "${YELLOW}[DEBUG]${NC} [$script_name] $message" +} +log_error() { + local message="$1" + local script_name="${2:-apt-layer}" + echo -e "${RED}[ERROR]${NC} [$script_name] $message" >&2 +} +log_warning() { + local message="$1" + local script_name="${2:-apt-layer}" + echo -e "${YELLOW}[WARNING]${NC} [$script_name] $message" >&2 +} +log_success() { + local message="$1" + local script_name="${2:-apt-layer}" + echo -e "${GREEN}[SUCCESS]${NC} [$script_name] $message" +} +log_layer() { + local message="$1" + local script_name="${2:-apt-layer}" + echo -e "${PURPLE}[LAYER]${NC} [$script_name] $message" +} +log_transaction() { + local message="$1" + local script_name="${2:-apt-layer}" + echo -e "${CYAN}[TRANSACTION]${NC} [$script_name] $message" +} + + +# Source apt-layer configuration (if available, skip for help commands) +# Skip configuration loading for help commands to avoid permission issues +if [[ "${1:-}" != "--help" && "${1:-}" != "-h" && "${1:-}" != "--help-full" && "${1:-}" != "--examples" ]]; then + if [[ -f "/usr/local/etc/apt-layer-config.sh" ]]; then + source "/usr/local/etc/apt-layer-config.sh" + log_info "Loaded apt-layer configuration" "apt-layer" + else + log_warning "apt-layer configuration not found, using defaults" "apt-layer" + fi +else + log_info "Skipping configuration loading for help command" "apt-layer" +fi + + +# ============================================================================ +# Header and Shared Functions +# ============================================================================ +# Utility functions for Particle-OS apt-layer Tool +# These functions provide system introspection and core utilities + +# Fallback logging functions (in case particle-config.sh is not available) +if ! declare -F log_info >/dev/null 2>&1; then + log_info() { + local message="$1" + local script_name="${2:-apt-layer}" + echo "[INFO] $message" + } +fi + +if ! declare -F log_warning >/dev/null 2>&1; then + log_warning() { + local message="$1" + local script_name="${2:-apt-layer}" + echo "[WARNING] $message" + } +fi + +if ! declare -F log_error >/dev/null 2>&1; then + log_error() { + local message="$1" + local script_name="${2:-apt-layer}" + echo "[ERROR] $message" >&2 + } +fi + +if ! declare -F log_success >/dev/null 2>&1; then + log_success() { + local message="$1" + local script_name="${2:-apt-layer}" + echo "[SUCCESS] $message" + } +fi + +if ! declare -F log_debug >/dev/null 2>&1; then + log_debug() { + local message="$1" + local script_name="${2:-apt-layer}" + echo "[DEBUG] $message" + } +fi + +if ! declare -F log_transaction >/dev/null 2>&1; then + log_transaction() { + local message="$1" + local script_name="${2:-apt-layer}" + echo "[TRANSACTION] $message" + } +fi + +if ! declare -F log_layer >/dev/null 2>&1; then + log_layer() { + local message="$1" + local script_name="${2:-apt-layer}" + echo "[LAYER] $message" + } +fi + +# Global variables for cleanup +CLEANUP_DIRS=() +CLEANUP_MOUNTS=() +CLEANUP_FILES=() + +# Workspace and directory variables +WORKSPACE="/var/lib/particle-os" +BUILD_DIR="/var/lib/particle-os/build" +LIVE_OVERLAY_DIR="/var/lib/particle-os/live-overlay" +COMPOSEFS_DIR="/var/lib/particle-os/composefs" +COMPOSEFS_SCRIPT="/usr/local/bin/composefs-alternative.sh" +CONTAINER_RUNTIME="podman" + +# Transaction state variables +TRANSACTION_ID="" +TRANSACTION_PHASE="" +TRANSACTION_TARGET="" +TRANSACTION_BACKUP="" +TRANSACTION_TEMP_DIRS=() +TRANSACTION_STATE="/var/lib/particle-os/transaction-state" +TRANSACTION_LOG="/var/lib/particle-os/transaction.log" + +# Trap for cleanup on exit +cleanup_on_exit() { + local exit_code=$? + + if [[ -n "$TRANSACTION_ID" ]]; then + log_transaction "Cleaning up transaction $TRANSACTION_ID (exit code: $exit_code)" "apt-layer" + + # Clean up temporary directories + for temp_dir in "${TRANSACTION_TEMP_DIRS[@]}"; do + if [[ -d "$temp_dir" ]]; then + log_debug "Cleaning up temporary directory: $temp_dir" "apt-layer" + rm -rf "$temp_dir" 2>/dev/null || true + fi + done + + # If transaction failed, attempt rollback + if [[ $exit_code -ne 0 ]] && [[ -n "$TRANSACTION_BACKUP" ]]; then + log_warning "Transaction failed, attempting rollback..." "apt-layer" + rollback_transaction + fi + + # Clear transaction state + clear_transaction_state + fi + + # Clean up any remaining mounts + cleanup_mounts + + exit $exit_code +} + +trap cleanup_on_exit EXIT INT TERM + +# SECURITY: Validate and sanitize input paths +validate_path() { + local path="$1" + local type="$2" + + # Check for null or empty paths + if [[ -z "$path" ]]; then + log_error "Empty $type path provided" "apt-layer" + exit 1 + fi + + # Check for path traversal attempts + if [[ "$path" =~ \.\. ]]; then + log_error "Path traversal attempt detected in $type: $path" "apt-layer" + exit 1 + fi + + # Check for absolute paths only (for source directories and mount points) + if [[ "$type" == "source_dir" || "$type" == "mount_point" ]]; then + if [[ ! "$path" =~ ^/ ]]; then + log_error "$type must be an absolute path: $path" "apt-layer" + exit 1 + fi + fi + + # Validate characters (alphanumeric, hyphens, underscores, slashes, dots) + if [[ ! "$path" =~ ^[a-zA-Z0-9/._-]+$ ]]; then + log_error "Invalid characters in $type: $path" "apt-layer" + exit 1 + fi + + echo "$path" +} + +# SECURITY: Validate image name (alphanumeric, hyphens, underscores only) +validate_image_name() { + local name="$1" + + if [[ -z "$name" ]]; then + log_error "Empty image name provided" "apt-layer" + exit 1 + fi + + if [[ ! "$name" =~ ^[a-zA-Z0-9/_-]+$ ]]; then + log_error "Invalid image name: $name (only alphanumeric, hyphens, underscores, and slashes allowed)" "apt-layer" + exit 1 + fi + + echo "$name" +} + +# Check if running as root +check_root() { + if [[ $EUID -ne 0 ]]; then + log_error "This script must be run as root" "apt-layer" + exit 1 + fi +} + +# Require root privileges for specific operations +require_root() { + local operation="${1:-this operation}" + if [[ $EUID -ne 0 ]]; then + log_error "Root privileges required for: $operation" "apt-layer" + log_info "Please run with sudo" "apt-layer" + exit 1 + fi +} + +# Check if system needs initialization +check_initialization_needed() { + local needs_init=false + local missing_items=() + + # Check for configuration file + if [[ ! -f "/usr/local/etc/particle-config.sh" ]]; then + needs_init=true + missing_items+=("configuration file") + fi + + # Check for workspace directory + if [[ ! -d "$WORKSPACE" ]]; then + needs_init=true + missing_items+=("workspace directory") + fi + + # Check for log directory + if [[ ! -d "/var/log/particle-os" ]]; then + needs_init=true + missing_items+=("log directory") + fi + + # Check for cache directory + if [[ ! -d "/var/cache/particle-os" ]]; then + needs_init=true + missing_items+=("cache directory") + fi + + if [[ "$needs_init" == "true" ]]; then + log_error "Particle-OS system not initialized. Missing: ${missing_items[*]}" "apt-layer" + log_info "Run 'sudo $0 --init' to initialize the system" "apt-layer" + exit 1 + fi +} + +# Initialize required directories and files with proper error handling +initialize_directories() { + log_info "Initializing Particle-OS directories and files..." "apt-layer" + + # Create main directories with proper error handling + local dirs=( + "$WORKSPACE" + "$BUILD_DIR" + "$LIVE_OVERLAY_DIR" + "$COMPOSEFS_DIR" + "/var/log/particle-os" + "/var/cache/particle-os" + "/usr/local/etc/particle-os" + ) + + for dir in "${dirs[@]}"; do + if ! mkdir -p "$dir" 2>/dev/null; then + log_warning "Failed to create directory $dir, attempting with sudo..." "apt-layer" + if ! sudo mkdir -p "$dir" 2>/dev/null; then + log_error "Failed to create directory: $dir" "apt-layer" + return 1 + fi + fi + + # Set proper permissions even if directory already exists + if [[ -d "$dir" ]]; then + sudo chown root:root "$dir" 2>/dev/null || true + sudo chmod 755 "$dir" 2>/dev/null || true + fi + done + + # Create required files with proper error handling + local files=( + "$WORKSPACE/current-deployment" + "$WORKSPACE/pending-deployment" + "$WORKSPACE/deployments.json" + "$TRANSACTION_STATE" + "$TRANSACTION_LOG" + ) + + for file in "${files[@]}"; do + if ! touch "$file" 2>/dev/null; then + log_warning "Failed to create file $file, attempting with sudo..." "apt-layer" + if ! sudo touch "$file" 2>/dev/null; then + log_error "Failed to create file: $file" "apt-layer" + return 1 + fi + fi + + # Set proper permissions even if file already exists + if [[ -f "$file" ]]; then + sudo chown root:root "$file" 2>/dev/null || true + sudo chmod 644 "$file" 2>/dev/null || true + fi + done + + # Initialize deployment database if it doesn't exist or is empty + if [[ ! -f "$WORKSPACE/deployments.json" ]] || [[ ! -s "$WORKSPACE/deployments.json" ]]; then + if ! cat > "$WORKSPACE/deployments.json" << 'EOF' +{ + "deployments": {}, + "current_deployment": null, + "pending_deployment": null, + "deployment_counter": 0, + "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" +} +EOF + then + log_warning "Failed to create deployment database, attempting with sudo..." "apt-layer" + if ! sudo tee "$WORKSPACE/deployments.json" > /dev/null << 'EOF' +{ + "deployments": {}, + "current_deployment": null, + "pending_deployment": null, + "deployment_counter": 0, + "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" +} +EOF + then + log_error "Failed to create deployment database" "apt-layer" + return 1 + fi + fi + + # Set proper permissions for deployment database + sudo chown root:root "$WORKSPACE/deployments.json" 2>/dev/null || true + sudo chmod 644 "$WORKSPACE/deployments.json" 2>/dev/null || true + log_success "Deployment database initialized" "apt-layer" + fi + + log_success "Particle-OS directories and files initialized successfully" "apt-layer" + return 0 +} + +# Initialize workspace +init_workspace() { + log_info "Initializing Particle-OS workspace..." "apt-layer" + + mkdir -p "$WORKSPACE" + mkdir -p "$BUILD_DIR" + mkdir -p "$LIVE_OVERLAY_DIR" + + # Ensure ComposeFS directory exists + if [[ ! -d "$COMPOSEFS_DIR" ]]; then + log_info "ComposeFS directory not found, initializing..." "apt-layer" + if [[ -f "$COMPOSEFS_SCRIPT" ]]; then + # Run composefs-alternative.sh status to initialize directories + "$COMPOSEFS_SCRIPT" status >/dev/null 2>&1 || true + fi + fi + + log_success "Workspace initialized: $WORKSPACE" "apt-layer" +} + +# ComposeFS helper functions +composefs_create() { + local image_name="$1" + local source_dir="$2" + + log_debug "Creating ComposeFS image: $image_name from $source_dir" "apt-layer" + + if ! "$COMPOSEFS_SCRIPT" create "$image_name" "$source_dir"; then + log_error "Failed to create ComposeFS image: $image_name" "apt-layer" + return 1 + fi + + log_success "ComposeFS image created: $image_name" "apt-layer" + return 0 +} + +composefs_mount() { + local image_name="$1" + local mount_point="$2" + + log_debug "Mounting ComposeFS image: $image_name to $mount_point" "apt-layer" + + if ! "$COMPOSEFS_SCRIPT" mount "$image_name" "$mount_point"; then + log_error "Failed to mount ComposeFS image: $image_name to $mount_point" "apt-layer" + return 1 + fi + + log_success "ComposeFS image mounted: $image_name to $mount_point" "apt-layer" + return 0 +} + +composefs_unmount() { + local mount_point="$1" + + log_debug "Unmounting ComposeFS image from: $mount_point" "apt-layer" + + if ! "$COMPOSEFS_SCRIPT" unmount "$mount_point"; then + log_error "Failed to unmount ComposeFS image from: $mount_point" "apt-layer" + return 1 + fi + + log_success "ComposeFS image unmounted from: $mount_point" "apt-layer" + return 0 +} + +composefs_list_images() { + log_debug "Listing ComposeFS images" "apt-layer" + "$COMPOSEFS_SCRIPT" list-images +} + +composefs_image_exists() { + local image_name="$1" + + # Check if image exists by trying to list it + if "$COMPOSEFS_SCRIPT" list-images | grep -q "^$image_name$"; then + return 0 + else + return 1 + fi +} + +composefs_remove_image() { + local image_name="$1" + + log_debug "Removing ComposeFS image: $image_name" "apt-layer" + + if ! "$COMPOSEFS_SCRIPT" remove "$image_name"; then + log_error "Failed to remove ComposeFS image: $image_name" "apt-layer" + return 1 + fi + + log_success "ComposeFS image removed: $image_name" "apt-layer" + return 0 +} + +# List all available branches/images +list_branches() { + log_info "Listing available ComposeFS images/branches..." "apt-layer" + + if ! composefs_list_images; then + log_error "Failed to list ComposeFS images" "apt-layer" + return 1 + fi + + return 0 +} + +# Show information about a specific branch/image +show_branch_info() { + local image_name="$1" + + log_info "Showing information for image: $image_name" "apt-layer" + + if ! composefs_image_exists "$image_name"; then + log_error "Image not found: $image_name" "apt-layer" + return 1 + fi + + # Get basic image information + echo "Image: $image_name" + echo "Status: Available" + + # Try to get more detailed information if available + if [[ -f "$COMPOSEFS_DIR/$image_name/info.json" ]]; then + echo "Details:" + jq -r '.' "$COMPOSEFS_DIR/$image_name/info.json" 2>/dev/null || echo " (Unable to parse info.json)" + fi + + return 0 +} + +# Remove an image (alias for composefs_remove_image) +remove_image() { + local image_name="$1" + + log_info "Removing image: $image_name" "apt-layer" + + if ! composefs_remove_image "$image_name"; then + log_error "Failed to remove image: $image_name" "apt-layer" + return 1 + fi + + return 0 +} + +composefs_get_status() { + log_debug "Getting ComposeFS status" "apt-layer" + "$COMPOSEFS_SCRIPT" status +} + +# Atomic directory operations +atomic_directory_swap() { + local source="$1" + local target="$2" + local backup="$3" + + log_debug "Performing atomic directory swap: $source -> $target (backup: $backup)" "apt-layer" + + # Create backup if specified + if [[ -n "$backup" ]] && [[ -d "$target" ]]; then + if ! mv "$target" "$backup"; then + log_error "Failed to create backup: $target -> $backup" "apt-layer" + return 1 + fi + log_debug "Backup created: $target -> $backup" "apt-layer" + fi + + # Move source to target + if ! mv "$source" "$target"; then + log_error "Failed to move source to target: $source -> $target" "apt-layer" + + # Restore backup if it exists + if [[ -n "$backup" ]] && [[ -d "$backup" ]]; then + log_warning "Restoring backup after failed move" "apt-layer" + mv "$backup" "$target" 2>/dev/null || true + fi + + return 1 + fi + + log_debug "Atomic directory swap completed: $source -> $target" "apt-layer" + return 0 +} + +# Cleanup mounts +cleanup_mounts() { + log_debug "Cleaning up mounts" "apt-layer" + + for mount in "${CLEANUP_MOUNTS[@]}"; do + if mountpoint -q "$mount" 2>/dev/null; then + log_debug "Unmounting: $mount" "apt-layer" + umount "$mount" 2>/dev/null || log_warning "Failed to unmount: $mount" "apt-layer" + fi + done +} + +# Get system information +get_system_info() { + echo "Kernel: $(uname -r)" + echo "Architecture: $(uname -m)" + echo "Available modules:" + if modprobe -n squashfs >/dev/null 2>&1; then + echo " squashfs module available" + else + echo " squashfs module not available" + fi + if modprobe -n overlay >/dev/null 2>&1; then + echo " overlay module available" + else + echo " overlay module not available" + fi +} + +# Calculate disk usage +calculate_disk_usage() { + local path="$1" + local size + size=$(du -sb "$path" 2>/dev/null | cut -f1 || echo "0") + local size_mb=$((size / 1024 / 1024)) + echo "$size_mb" +} + +# Get available space +get_available_space() { + local path="$1" + local available_space + available_space=$(df "$path" | tail -1 | awk '{print $4}') + local available_space_mb=$((available_space * 1024 / 1024 / 1024)) + echo "$available_space_mb" +} + +# --- END OF SCRIPTLET: 00-header.sh --- + +# ============================================================================ +# Dependency Checking and Validation +# ============================================================================ +# Enhanced dependency checking and validation for Particle-OS apt-layer Tool +check_dependencies() { + local command_type="${1:-}" + local packages=("${@:2}") + + log_info "Checking dependencies for command: ${command_type:-general}" "apt-layer" + + local missing_deps=() + local missing_packages=() + local missing_tools=() + local missing_scripts=() + local missing_modules=() + + # Core system dependencies + local core_deps=("chroot" "apt-get" "dpkg" "jq" "mount" "umount") + for dep in "${core_deps[@]}"; do + if ! command -v "$dep" >/dev/null 2>&1; then + missing_deps+=("$dep") + missing_tools+=("$dep") + fi + done + + # Container-specific dependencies + if [[ "$command_type" == "--container" || "$command_type" == "container" ]]; then + local container_deps=("podman" "docker" "systemd-nspawn") + local found_container=false + + for dep in "${container_deps[@]}"; do + if command -v "$dep" >/dev/null 2>&1; then + found_container=true + break + fi + done + + if [[ "$found_container" == "false" ]]; then + missing_deps+=("container-runtime") + missing_tools+=("podman, docker, or systemd-nspawn") + fi + fi + + # ComposeFS-specific dependencies + if [[ "$command_type" == "--composefs" || "$command_type" == "composefs" ]]; then + local composefs_deps=("mksquashfs" "unsquashfs" "skopeo") + for dep in "${composefs_deps[@]}"; do + if ! command -v "$dep" >/dev/null 2>&1; then + missing_deps+=("$dep") + missing_tools+=("$dep") + fi + done + fi + + # Security scanning dependencies + if [[ "$command_type" == "--scan" || "$command_type" == "security" ]]; then + local security_deps=("curl" "wget" "gpg") + for dep in "${security_deps[@]}"; do + if ! command -v "$dep" >/dev/null 2>&1; then + missing_deps+=("$dep") + missing_tools+=("$dep") + fi + done + fi + + # Check for required scripts + local required_scripts=( + "composefs-alternative.sh:/usr/local/bin/composefs-alternative.sh" + "bootc-alternative.sh:/usr/local/bin/bootc-alternative.sh" + "bootupd-alternative.sh:/usr/local/bin/bootupd-alternative.sh" + ) + + for script_info in "${required_scripts[@]}"; do + local script_name="${script_info%%:*}" + local script_path="${script_info##*:}" + + if [[ ! -f "$script_path" ]]; then + missing_deps+=("$script_name") + missing_scripts+=("$script_name") + elif [[ ! -x "$script_path" ]]; then + missing_deps+=("$script_name (not executable)") + missing_scripts+=("$script_name (needs chmod +x)") + fi + done + + # Check for kernel modules + check_kernel_modules + + # Validate package names if provided + if [[ ${#packages[@]} -gt 0 ]]; then + if ! validate_package_names "${packages[@]}"; then + return 1 + fi + fi + + # Report missing dependencies with specific installation instructions + if [[ ${#missing_deps[@]} -gt 0 ]]; then + echo "" + log_error "Missing dependencies detected!" "apt-layer" + echo "" + + if [[ ${#missing_tools[@]} -gt 0 ]]; then + echo " Missing system packages:" + for tool in "${missing_tools[@]}"; do + echo " $tool" + done + echo "" + echo " Install with: sudo apt install -y ${missing_tools[*]}" + echo "" + fi + + if [[ ${#missing_scripts[@]} -gt 0 ]]; then + echo " Missing or non-executable scripts:" + for script in "${missing_scripts[@]}"; do + echo " $script" + done + echo "" + echo " Ensure scripts are installed and executable:" + echo " sudo chmod +x /usr/local/bin/*-alternative.sh" + echo "" + fi + + if [[ ${#missing_modules[@]} -gt 0 ]]; then + echo " Missing kernel modules:" + for module in "${missing_modules[@]}"; do + echo " $module" + done + echo "" + echo " Load modules with: sudo modprobe ${missing_modules[*]}" + echo " Or install with: sudo apt install linux-modules-extra-\$(uname -r)" + echo "" + fi + + echo " Quick fix for common dependencies:" + echo " sudo apt install -y squashfs-tools jq coreutils util-linux" + echo "" + echo " For more information, run: apt-layer --help" + echo "" + + exit 1 + fi + + log_success "All dependencies found and validated" "apt-layer" +} + +# Check kernel modules +check_kernel_modules() { + log_debug "Checking kernel modules..." "apt-layer" + + local missing_modules=() + local required_modules=("squashfs" "overlay" "fuse") + + for module in "${required_modules[@]}"; do + if ! modprobe -n "$module" >/dev/null 2>&1; then + missing_modules+=("$module") + fi + done + + if [[ ${#missing_modules[@]} -gt 0 ]]; then + log_warning "Missing kernel modules: ${missing_modules[*]}" "apt-layer" + log_info "Load modules with: sudo modprobe ${missing_modules[*]}" "apt-layer" + log_info "Or install with: sudo apt install linux-modules-extra-$(uname -r)" "apt-layer" + + # Store missing modules for the main error report + missing_modules_global=("${missing_modules[@]}") + else + log_debug "All required kernel modules available" "apt-layer" + fi +} + +# Check for OCI integration script +check_oci_integration() { + local oci_script="/usr/local/bin/oci-integration.sh" + + if [[ -f "$oci_script" ]] && [[ -x "$oci_script" ]]; then + log_debug "OCI integration script found: $oci_script" "apt-layer" + return 0 + else + log_warning "OCI integration script not found or not executable: $oci_script" "apt-layer" + log_info "OCI export/import features will not be available" "apt-layer" + return 1 + fi +} + +# Check for bootloader integration script +check_bootloader_integration() { + local bootloader_script="/usr/local/bin/bootloader-integration.sh" + + if [[ -f "$bootloader_script" ]] && [[ -x "$bootloader_script" ]]; then + log_debug "Bootloader integration script found: $bootloader_script" "apt-layer" + return 0 + else + log_warning "Bootloader integration script not found or not executable: $bootloader_script" "apt-layer" + log_info "Automatic bootloader integration will not be available" "apt-layer" + return 1 + fi +} + +# Validate package names +validate_package_names() { + local packages=("$@") + local invalid_packages=() + + for package in "${packages[@]}"; do + # Check for basic package name format + if [[ ! "$package" =~ ^[a-zA-Z0-9][a-zA-Z0-9+.-]*$ ]]; then + invalid_packages+=("$package") + fi + done + + if [ ${#invalid_packages[@]} -ne 0 ]; then + log_error "Invalid package names: ${invalid_packages[*]}" "apt-layer" + log_info "Package names must contain only alphanumeric characters, +, -, and ." "apt-layer" + return 1 + fi + + return 0 +} + +# Check available disk space +check_disk_space() { + local required_space_mb="$1" + local target_dir="${2:-$WORKSPACE}" + + local available_space_mb + available_space_mb=$(get_available_space "$target_dir") + + if [[ $available_space_mb -lt $required_space_mb ]]; then + log_error "Insufficient disk space: ${available_space_mb}MB available, need ${required_space_mb}MB" "apt-layer" + return 1 + fi + + log_debug "Disk space check passed: ${available_space_mb}MB available" "apt-layer" + return 0 +} + +# Check if system is in a bootable state +check_system_state() { + log_debug "Checking system state..." "apt-layer" + + # Check if running from a live system + if [[ -f "/run/ostree-booted" ]]; then + log_info "System is running from OSTree/ComposeFS" "apt-layer" + return 0 + fi + + # Check if running from a traditional system + if [[ -f "/etc/os-release" ]]; then + log_info "System is running from traditional filesystem" "apt-layer" + return 0 + fi + + log_warning "Unable to determine system state" "apt-layer" + return 1 +} + +# Enhanced error reporting with actionable messages +show_actionable_error() { + local error_type="$1" + local error_message="$2" + local command="${3:-}" + local packages="${4:-}" + + echo "" + log_error "$error_message" "apt-layer" + echo "" + + case "$error_type" in + "missing_dependencies") + echo " To fix this issue:" + echo " 1. Install missing dependencies:" + echo " sudo apt update" + echo " sudo apt install -y $packages" + echo "" + echo " 2. Ensure scripts are executable:" + echo " sudo chmod +x /usr/local/bin/*-alternative.sh" + echo "" + echo " 3. Load required kernel modules:" + echo " sudo modprobe squashfs overlay fuse" + echo "" + ;; + "permission_denied") + echo " Permission issue detected:" + echo " This command requires root privileges." + echo "" + echo " Run with sudo:" + echo " sudo apt-layer $command" + echo "" + ;; + "invalid_arguments") + echo " Invalid arguments provided:" + echo " Check the command syntax and try again." + echo "" + echo " For help, run:" + echo " apt-layer --help" + echo " apt-layer $command --help" + echo "" + ;; + "system_not_initialized") + echo " System not initialized:" + echo " Particle-OS needs to be initialized first." + echo "" + echo " Run initialization:" + echo " sudo apt-layer --init" + echo "" + ;; + "disk_space") + echo " Insufficient disk space:" + echo " Free up space or use a different location." + echo "" + echo " Check available space:" + echo " df -h" + echo "" + ;; + *) + echo " Unknown error occurred." + echo " Please check the error message above." + echo "" + echo " For help, run: apt-layer --help" + echo "" + ;; + esac + + echo " For more information:" + echo " apt-layer --help" + echo " apt-layer --help-full" + echo " apt-layer --examples" + echo "" +} + +# Pre-flight validation before any command +pre_flight_check() { + local command_type="$1" + local packages=("${@:2}") + + log_info "Running pre-flight checks..." "apt-layer" + + # Check if running as root for privileged operations + if [[ "$command_type" =~ ^(install|upgrade|rebase|rollback|init|live-overlay)$ ]]; then + if [[ $EUID -ne 0 ]]; then + show_actionable_error "permission_denied" "This command requires root privileges" "$command_type" + exit 1 + fi + fi + + # Check system initialization (skip for help commands) + if [[ "$command_type" != "--init" && "$command_type" != "init" && "$command_type" != "--help" && "$command_type" != "-h" && "$command_type" != "--help-full" && "$command_type" != "--examples" ]]; then + if [[ ! -f "/usr/local/etc/particle-config.sh" ]]; then + show_actionable_error "system_not_initialized" "Particle-OS system not initialized" "$command_type" + exit 1 + fi + fi + + # Check dependencies + if ! check_dependencies "$command_type" "${packages[@]}"; then + exit 1 + fi + + # Check disk space for operations that create files + if [[ "$command_type" =~ ^(create|build|install|upgrade)$ ]]; then + if ! check_disk_space 1000; then + show_actionable_error "disk_space" "Insufficient disk space for operation" "$command_type" + exit 1 + fi + fi + + log_success "Pre-flight checks passed" "apt-layer" +} + +# --- END OF SCRIPTLET: 01-dependencies.sh --- + +# ============================================================================ +# Transaction Management +# ============================================================================ +# Transaction management for apt-layer Tool +# Provides atomic operations with automatic rollback and recovery + +# System initialization functions +initialize_apt_layer_system() { + log_info "Initializing apt-layer system..." "apt-layer" + + # Use the new system initialization function if available + if command -v init_apt_layer_system >/dev/null 2>&1; then + init_apt_layer_system + else + # Fallback to basic initialization + log_info "Using fallback initialization..." "apt-layer" + + # Create configuration directory + mkdir -p "/usr/local/etc/apt-layer" + + # Create workspace directory + mkdir -p "/var/lib/apt-layer" + + # Create log directory + mkdir -p "/var/log/apt-layer" + + # Create cache directory + mkdir -p "/var/cache/apt-layer" + + # Create configuration file if it doesn't exist + if [[ ! -f "/usr/local/etc/apt-layer-config.sh" ]]; then + create_default_configuration + fi + + # Set proper permissions + chmod 755 "/var/lib/apt-layer" + chmod 755 "/var/log/apt-layer" + chmod 755 "/var/cache/apt-layer" + chmod 644 "/usr/local/etc/apt-layer-config.sh" + + log_success "apt-layer system initialized successfully (fallback)" "apt-layer" + fi +} + +create_default_configuration() { + log_info "Creating default configuration..." "apt-layer" + + cat > "/usr/local/etc/apt-layer-config.sh" << 'EOF' +#!/bin/bash +# apt-layer Configuration File +# Generated automatically on $(date) + +# Core paths +export APT_LAYER_WORKSPACE="/var/lib/apt-layer" +export APT_LAYER_CONFIG_DIR="/usr/local/etc/apt-layer" +export APT_LAYER_LOG_DIR="/var/log/apt-layer" +export APT_LAYER_CACHE_DIR="/var/cache/apt-layer" + +# Build and temporary directories +export APT_LAYER_BUILD_DIR="$APT_LAYER_WORKSPACE/build" +export APT_LAYER_TEMP_DIR="$APT_LAYER_WORKSPACE/temp" +export APT_LAYER_BACKUP_DIR="$APT_LAYER_WORKSPACE/backup" + +# Layer management +export APT_LAYER_LAYERS_DIR="$APT_LAYER_WORKSPACE/layers" +export APT_LAYER_IMAGES_DIR="$APT_LAYER_WORKSPACE/images" +export APT_LAYER_MOUNTS_DIR="$APT_LAYER_WORKSPACE/mounts" + +# ComposeFS integration +export APT_LAYER_COMPOSEFS_DIR="$APT_LAYER_WORKSPACE/composefs" +export APT_LAYER_COMPOSEFS_SCRIPT="/usr/local/bin/composefs-alternative.sh" + +# Boot management +export APT_LAYER_BOOTC_SCRIPT="/usr/local/bin/bootc-alternative.sh" +export APT_LAYER_BOOTUPD_SCRIPT="/usr/local/bin/bootupd-alternative.sh" + +# Transaction management +export APT_LAYER_TRANSACTION_LOG="$APT_LAYER_LOG_DIR/transactions.log" +export APT_LAYER_TRANSACTION_STATE="$APT_LAYER_CACHE_DIR/transaction.state" + +# Logging configuration +export APT_LAYER_LOG_LEVEL="INFO" +export APT_LAYER_LOG_FILE="$APT_LAYER_LOG_DIR/apt-layer.log" + +# Security settings +export APT_LAYER_SIGNING_ENABLED="false" +export APT_LAYER_VERIFY_SIGNATURES="false" + +# Container settings +export APT_LAYER_CONTAINER_RUNTIME="podman" +export APT_LAYER_CHROOT_ENABLED="true" + +# Default package sources +export APT_LAYER_DEFAULT_SOURCES="main restricted universe multiverse" + +# Performance settings +export APT_LAYER_PARALLEL_JOBS="4" +export APT_LAYER_CACHE_ENABLED="true" + +# Load configuration if it exists +if [[ -f "$APT_LAYER_CONFIG_DIR/apt-layer-config.sh" ]]; then + source "$APT_LAYER_CONFIG_DIR/apt-layer-config.sh" +fi +EOF + + log_success "Default configuration created: /usr/local/etc/apt-layer-config.sh" "apt-layer" +} + +reset_apt_layer_system() { + log_warning "Resetting apt-layer system..." "apt-layer" + + # Backup existing configuration + if [[ -f "/usr/local/etc/apt-layer-config.sh" ]]; then + cp "/usr/local/etc/apt-layer-config.sh" "/usr/local/etc/apt-layer-config.sh.backup.$(date +%Y%m%d_%H%M%S)" + log_info "Existing configuration backed up" "apt-layer" + fi + + # Remove existing directories + rm -rf "/var/lib/apt-layer" + rm -rf "/var/log/apt-layer" + rm -rf "/var/cache/apt-layer" + + # Reinitialize system + initialize_apt_layer_system + + log_success "apt-layer system reset successfully" "apt-layer" +} + +# Transaction management functions +start_transaction() { + local operation="$1" + local target="$2" + + TRANSACTION_ID=$(date +%Y%m%d_%H%M%S)_$$ + TRANSACTION_PHASE="started" + TRANSACTION_TARGET="$target" + + log_transaction "Starting transaction $TRANSACTION_ID: $operation -> $target" "apt-layer" + + # Save transaction state + save_transaction_state + + # Log transaction start + echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) - START - $TRANSACTION_ID - $operation - $target" >> "$TRANSACTION_LOG" +} + +update_transaction_phase() { + local phase="$1" + + TRANSACTION_PHASE="$phase" + + log_transaction "Transaction $TRANSACTION_ID phase: $phase" "apt-layer" + + # Update transaction state + save_transaction_state + + # Log phase update + echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) - PHASE - $TRANSACTION_ID - $phase" >> "$TRANSACTION_LOG" +} + +commit_transaction() { + log_transaction "Committing transaction $TRANSACTION_ID" "apt-layer" + + # Log successful completion + echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) - COMMIT - $TRANSACTION_ID - SUCCESS" >> "$TRANSACTION_LOG" + + # Clear transaction state + clear_transaction_state + + log_success "Transaction $TRANSACTION_ID completed successfully" "apt-layer" +} + +rollback_transaction() { + log_transaction "Rolling back transaction $TRANSACTION_ID" "apt-layer" + + if [[ -n "$TRANSACTION_BACKUP" ]] && [[ -d "$TRANSACTION_BACKUP" ]]; then + log_info "Restoring from backup: $TRANSACTION_BACKUP" "apt-layer" + + # Restore from backup + if atomic_directory_swap "$TRANSACTION_BACKUP" "$TRANSACTION_TARGET" ""; then + log_success "Rollback completed successfully" "apt-layer" + else + log_error "Rollback failed - manual intervention may be required" "apt-layer" + fi + else + log_warning "No backup available for rollback" "apt-layer" + fi + + # Log rollback + echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) - ROLLBACK - $TRANSACTION_ID - $TRANSACTION_PHASE" >> "$TRANSACTION_LOG" + + # Clear transaction state + clear_transaction_state +} + +save_transaction_state() { + if [[ -n "$TRANSACTION_ID" ]]; then + cat > "$TRANSACTION_STATE" << EOF +TRANSACTION_ID="$TRANSACTION_ID" +TRANSACTION_PHASE="$TRANSACTION_PHASE" +TRANSACTION_TARGET="$TRANSACTION_TARGET" +TRANSACTION_BACKUP="$TRANSACTION_BACKUP" +TRANSACTION_TEMP_DIRS=(${TRANSACTION_TEMP_DIRS[*]}) +EOF + fi +} + +clear_transaction_state() { + TRANSACTION_ID="" + TRANSACTION_PHASE="" + TRANSACTION_TARGET="" + TRANSACTION_BACKUP="" + TRANSACTION_TEMP_DIRS=() + + # Remove state file + rm -f "$TRANSACTION_STATE" +} + +load_transaction_state() { + if [[ -f "$TRANSACTION_STATE" ]]; then + source "$TRANSACTION_STATE" + return 0 + else + return 1 + fi +} + +check_incomplete_transactions() { + log_info "Checking for incomplete transactions..." "apt-layer" + + if load_transaction_state; then + log_warning "Found incomplete transaction: $TRANSACTION_ID (phase: $TRANSACTION_PHASE)" "apt-layer" + log_info "Target: $TRANSACTION_TARGET" "apt-layer" + + if [[ -n "$TRANSACTION_BACKUP" ]] && [[ -d "$TRANSACTION_BACKUP" ]]; then + log_info "Backup available: $TRANSACTION_BACKUP" "apt-layer" + fi + + # Ask user what to do + echo + echo "Incomplete transaction detected. Choose an action:" + echo "1) Attempt rollback (recommended)" + echo "2) Continue transaction (risky)" + echo "3) Clear transaction state (manual cleanup required)" + echo "4) Exit" + echo + read -p "Enter choice (1-4): " choice + + case "$choice" in + 1) + log_info "Attempting rollback..." "apt-layer" + rollback_transaction + ;; + 2) + log_warning "Continuing incomplete transaction..." "apt-layer" + log_info "Transaction will resume from phase: $TRANSACTION_PHASE" "apt-layer" + ;; + 3) + log_warning "Clearing transaction state..." "apt-layer" + clear_transaction_state + ;; + 4) + log_info "Exiting..." "apt-layer" + exit 0 + ;; + *) + log_error "Invalid choice, exiting..." "apt-layer" + exit 1 + ;; + esac + else + log_info "No incomplete transactions found" "apt-layer" + fi +} + +# Dry run functionality for package installation +dry_run_apt_install() { + local packages=("$@") + local chroot_dir="${1:-}" + + log_info "Performing dry run for packages: ${packages[*]}" "apt-layer" + + local apt_cmd + if [[ -n "$chroot_dir" ]]; then + apt_cmd="chroot '$chroot_dir' apt-get install --simulate" + else + apt_cmd="apt-get install --simulate" + fi + + # Add packages to command + apt_cmd+=" ${packages[*]}" + + log_debug "Running: $apt_cmd" "apt-layer" + + # Execute dry run + if eval "$apt_cmd" >/dev/null 2>&1; then + log_success "Dry run completed successfully - no conflicts detected" "apt-layer" + return 0 + else + log_error "Dry run failed - potential conflicts detected" "apt-layer" + log_info "Run the command manually to see detailed output:" "apt-layer" + log_info "$apt_cmd" "apt-layer" + return 1 + fi +} + +# Transaction logging utilities +log_transaction_event() { + local event="$1" + local details="$2" + + echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) - $event - $TRANSACTION_ID - $details" >> "$TRANSACTION_LOG" +} + +# Transaction validation +validate_transaction_state() { + if [[ -z "$TRANSACTION_ID" ]]; then + log_error "No active transaction" "apt-layer" + return 1 + fi + + if [[ -z "$TRANSACTION_TARGET" ]]; then + log_error "Transaction target not set" "apt-layer" + return 1 + fi + + return 0 +} + +# Transaction cleanup utilities +add_temp_directory() { + local temp_dir="$1" + TRANSACTION_TEMP_DIRS+=("$temp_dir") + save_transaction_state +} + +add_backup_path() { + local backup_path="$1" + TRANSACTION_BACKUP="$backup_path" + save_transaction_state +} + +# --- END OF SCRIPTLET: 02-transactions.sh --- + +# ============================================================================ +# Traditional Layer Creation +# ============================================================================ +# Traditional layer creation for Ubuntu uBlue apt-layer Tool +# Provides chroot-based package installation for layer creation + +# Create traditional layer +create_layer() { + local base_image="$1" + local new_image="$2" + shift 2 + local packages=("$@") + + log_layer "Creating traditional layer: $new_image" "apt-layer" + log_info "Base image: $base_image" "apt-layer" + log_info "Packages to install: ${packages[*]}" "apt-layer" + + # Start transaction + start_transaction "create_layer" "$new_image" + + # Check if base image exists + if ! composefs_image_exists "$base_image"; then + log_error "Base image '$base_image' not found" "apt-layer" + log_info "Available images:" "apt-layer" + composefs_list_images + exit 1 + fi + + # Prepare temp_layer_dir + local temp_layer_dir="$BUILD_DIR/temp-layer-$(basename "$new_image")-${TRANSACTION_ID}" + local final_layer_dir="$BUILD_DIR/layer-$(basename "$new_image")" + local backup_dir="$BUILD_DIR/backup-layer-$(basename "$new_image")-${TRANSACTION_ID}" + add_temp_directory "$temp_layer_dir" + add_temp_directory "$backup_dir" + rm -rf "$temp_layer_dir" 2>/dev/null || true + mkdir -p "$temp_layer_dir" + + update_transaction_phase "checkout_base" + + # Mount base image to temp_layer_dir + log_info "Mounting base image..." "apt-layer" + if ! composefs_mount "$base_image" "$temp_layer_dir"; then + log_error "Failed to mount base image" "apt-layer" + exit 1 + fi + + update_transaction_phase "setup_chroot" + + # Set up chroot environment + log_info "Setting up chroot environment..." "apt-layer" + mount --bind /proc "$temp_layer_dir/proc" + mount --bind /sys "$temp_layer_dir/sys" + mount --bind /dev "$temp_layer_dir/dev" + + # Copy host's resolv.conf for internet access + cp /etc/resolv.conf "$temp_layer_dir/etc/resolv.conf" 2>/dev/null || true + + # Ensure /run exists and is writable + mkdir -p "$temp_layer_dir/run" + chmod 755 "$temp_layer_dir/run" + + # Set non-interactive environment for apt + export DEBIAN_FRONTEND=noninteractive + + update_transaction_phase "dry_run_check" + + # Perform dry run to check for conflicts + log_info "Performing dry run to check for package conflicts..." "apt-layer" + if ! dry_run_apt_install "$temp_layer_dir" "${packages[@]}"; then + log_error "Dry run failed - package conflicts detected" "apt-layer" + log_info "Please resolve conflicts before proceeding" "apt-layer" + exit 1 + fi + + update_transaction_phase "install_packages" + + # Install packages in chroot + log_info "Installing packages in chroot..." "apt-layer" + if ! chroot "$temp_layer_dir" apt-get update; then + log_error "Failed to update package lists in chroot" "apt-layer" + exit 1 + fi + + if ! chroot "$temp_layer_dir" apt-get install -y "${packages[@]}"; then + log_error "Failed to install packages in chroot" "apt-layer" + exit 1 + fi + + # Clean up package cache + chroot "$temp_layer_dir" apt-get clean + chroot "$temp_layer_dir" apt-get autoremove -y + + update_transaction_phase "cleanup_mounts" + + # Clean up mounts + umount "$temp_layer_dir/proc" 2>/dev/null || true + umount "$temp_layer_dir/sys" 2>/dev/null || true + umount "$temp_layer_dir/dev" 2>/dev/null || true + + # Remove temporary resolv.conf + rm -f "$temp_layer_dir/etc/resolv.conf" + + update_transaction_phase "atomic_swap" + + # Perform atomic directory swap + if [[ -d "$final_layer_dir" ]]; then + log_debug "Backing up existing layer directory" "apt-layer" + if ! atomic_directory_swap "$final_layer_dir" "$backup_dir" ""; then + log_error "Failed to backup existing layer directory" "apt-layer" + exit 1 + fi + add_backup_path "$backup_dir" + fi + + # Move temporary directory to final location + if ! atomic_directory_swap "$temp_layer_dir" "$final_layer_dir" ""; then + log_error "Failed to perform atomic directory swap" "apt-layer" + exit 1 + fi + + update_transaction_phase "create_commit" + + # Create ComposeFS image from the final layer directory + log_info "Creating ComposeFS image..." "apt-layer" + if ! composefs_create "$new_image" "$final_layer_dir"; then + log_error "Failed to create ComposeFS image" "apt-layer" + exit 1 + fi + + # Commit transaction + commit_transaction + + log_success "Traditional layer created successfully: $new_image" "apt-layer" +} + +# Setup chroot environment for package installation +setup_chroot_environment() { + local chroot_dir="$1" + + log_debug "Setting up chroot environment: $chroot_dir" "apt-layer" + + # Create necessary directories + mkdir -p "$chroot_dir"/{proc,sys,dev,run} + + # Mount essential filesystems + mount --bind /proc "$chroot_dir/proc" + mount --bind /sys "$chroot_dir/sys" + mount --bind /dev "$chroot_dir/dev" + + # Copy DNS configuration + cp /etc/resolv.conf "$chroot_dir/etc/resolv.conf" 2>/dev/null || true + + # Set proper permissions + chmod 755 "$chroot_dir/run" + + # Set environment variables + export DEBIAN_FRONTEND=noninteractive + + log_debug "Chroot environment setup completed" "apt-layer" +} + +# Cleanup chroot environment +cleanup_chroot_environment() { + local chroot_dir="$1" + + log_debug "Cleaning up chroot environment: $chroot_dir" "apt-layer" + + # Unmount filesystems + umount "$chroot_dir/proc" 2>/dev/null || true + umount "$chroot_dir/sys" 2>/dev/null || true + umount "$chroot_dir/dev" 2>/dev/null || true + + # Remove temporary files + rm -f "$chroot_dir/etc/resolv.conf" + + log_debug "Chroot environment cleanup completed" "apt-layer" +} + +# Install packages in chroot with error handling +install_packages_in_chroot() { + local chroot_dir="$1" + shift + local packages=("$@") + + log_info "Installing packages in chroot: ${packages[*]}" "apt-layer" + + # Update package lists + if ! chroot "$chroot_dir" apt-get update; then + log_error "Failed to update package lists in chroot" "apt-layer" + return 1 + fi + + # Install packages + if ! chroot "$chroot_dir" apt-get install -y "${packages[@]}"; then + log_error "Failed to install packages in chroot" "apt-layer" + return 1 + fi + + # Clean up + chroot "$chroot_dir" apt-get clean + chroot "$chroot_dir" apt-get autoremove -y + + log_success "Packages installed successfully in chroot" "apt-layer" + return 0 +} + +# Validate chroot environment +validate_chroot_environment() { + local chroot_dir="$1" + + log_debug "Validating chroot environment: $chroot_dir" "apt-layer" + + # Check if chroot directory exists + if [[ ! -d "$chroot_dir" ]]; then + log_error "Chroot directory does not exist: $chroot_dir" "apt-layer" + return 1 + fi + + # Check if essential directories exist + for dir in bin lib usr etc; do + if [[ ! -d "$chroot_dir/$dir" ]]; then + log_error "Essential directory missing in chroot: $dir" "apt-layer" + return 1 + fi + done + + # Check if apt is available + if [[ ! -x "$chroot_dir/usr/bin/apt-get" ]]; then + log_error "apt-get not found in chroot environment" "apt-layer" + return 1 + fi + + log_debug "Chroot environment validation passed" "apt-layer" + return 0 +} + +# --- END OF SCRIPTLET: 03-traditional.sh --- + +# ============================================================================ +# Container-based Layer Creation (Apx-style) +# ============================================================================ +# Container-based layer creation for Ubuntu uBlue apt-layer Tool +# Provides Apx-style isolated container installation with ComposeFS backend + +# Container runtime detection and configuration +detect_container_runtime() { + log_info "Detecting container runtime" "apt-layer" + + # Check for podman first (preferred for rootless) + if command -v podman &> /dev/null; then + CONTAINER_RUNTIME="podman" + log_info "Using podman as container runtime" "apt-layer" + return 0 + fi + + # Fallback to docker + if command -v docker &> /dev/null; then + CONTAINER_RUNTIME="docker" + log_info "Using docker as container runtime" "apt-layer" + return 0 + fi + + # Check for systemd-nspawn as last resort + if command -v systemd-nspawn &> /dev/null; then + CONTAINER_RUNTIME="systemd-nspawn" + log_info "Using systemd-nspawn as container runtime" "apt-layer" + return 0 + fi + + log_error "No supported container runtime found (podman, docker, or systemd-nspawn)" "apt-layer" + return 1 +} + +# Enhanced container runtime detection with validation +init_container_system() { + log_info "Initializing container system" "apt-layer" + + # Detect container runtime + if ! detect_container_runtime; then + return 1 + fi + + # Validate container runtime + if ! validate_container_runtime "$CONTAINER_RUNTIME"; then + return 1 + fi + + # Ensure workspace directories exist + mkdir -p "$WORKSPACE"/{images,temp,containers} + + log_success "Container system initialized with runtime: $CONTAINER_RUNTIME" "apt-layer" + return 0 +} + +# Validate container runtime capabilities +validate_container_runtime() { + local runtime="$1" + + log_info "Validating container runtime: $runtime" "apt-layer" + + case "$runtime" in + podman) + if ! podman info &> /dev/null; then + log_error "podman is not properly configured" "apt-layer" + return 1 + fi + ;; + docker) + if ! docker info &> /dev/null; then + log_error "docker is not properly configured" "apt-layer" + return 1 + fi + ;; + systemd-nspawn) + # systemd-nspawn doesn't need special validation + ;; + *) + log_error "Unsupported container runtime: $runtime" "apt-layer" + return 1 + ;; + esac + + log_success "Container runtime validation passed" "apt-layer" + return 0 +} + +# Determine if base image is ComposeFS or OCI +is_composefs_image() { + local base_image="$1" + + # Check if it's a ComposeFS image path + if [[ "$base_image" == *"/"* ]] && [[ -d "$WORKSPACE/images/$base_image" ]]; then + return 0 # True - it's a ComposeFS image + fi + + return 1 # False - it's likely an OCI image +} + +# Export ComposeFS image to OCI format for container use +export_composefs_to_oci() { + local composefs_image="$1" + local temp_oci_dir="$2" + + log_info "Exporting ComposeFS image to OCI format: $composefs_image" "apt-layer" + + # Create temporary OCI directory structure + mkdir -p "$temp_oci_dir"/{blobs,refs} + + # Use ComposeFS backend to export (placeholder for now) + # This will be fully implemented when 06-oci-integration.sh is complete + if [[ -f "$COMPOSEFS_SCRIPT" ]]; then + # Temporary: mount and copy filesystem + local mount_point="$temp_oci_dir/mount" + mkdir -p "$mount_point" + + if mount_composefs_image "$composefs_image" "$mount_point"; then + # Create a simple OCI-like structure + mkdir -p "$temp_oci_dir/rootfs" + cp -a "$mount_point"/* "$temp_oci_dir/rootfs/" 2>/dev/null || true + umount "$mount_point" 2>/dev/null || true + log_success "ComposeFS image exported to OCI format" "apt-layer" + return 0 + else + log_error "Failed to mount ComposeFS image for export" "apt-layer" + return 1 + fi + else + log_error "ComposeFS script not found for export" "apt-layer" + return 1 + fi +} + +# Create base container image for layer creation +create_base_container_image() { + local base_image="$1" + local container_name="$2" + + log_info "Creating base container image: $base_image" "apt-layer" + + # Determine if base_image is ComposeFS or OCI + if is_composefs_image "$base_image"; then + log_info "Base image is ComposeFS image: $base_image" "apt-layer" + + # Export ComposeFS image to OCI format for container use + local temp_oci_dir="$WORKSPACE/temp/oci-export-$$" + if ! export_composefs_to_oci "$base_image" "$temp_oci_dir"; then + log_error "Failed to export ComposeFS image to OCI format" "apt-layer" + return 1 + fi + + # Use the exported OCI image + log_success "ComposeFS image exported and ready for container use" "apt-layer" + return 0 + else + log_info "Base image is OCI image: $base_image" "apt-layer" + + # Pull standard OCI image if needed + case "$CONTAINER_RUNTIME" in + podman) + if ! podman image exists "$base_image"; then + log_info "Pulling OCI image: $base_image" "apt-layer" + podman pull "$base_image" + fi + ;; + docker) + if ! docker image ls "$base_image" &> /dev/null; then + log_info "Pulling OCI image: $base_image" "apt-layer" + docker pull "$base_image" + fi + ;; + systemd-nspawn) + # systemd-nspawn uses host filesystem + log_info "Using host filesystem for systemd-nspawn" "apt-layer" + ;; + esac + + log_success "OCI base image ready: $base_image" "apt-layer" + return 0 + fi +} + +# Container-based package installation +container_install_packages() { + local base_image="$1" + local new_image="$2" + local packages=("${@:3}") + + log_info "Container-based package installation: ${packages[*]}" "apt-layer" + + # Create temporary container name + local container_name="apt-layer-$(date +%s)-$$" + local temp_dir="$WORKSPACE/temp/$container_name" + + # Ensure temp directory exists + mkdir -p "$temp_dir" + + # Start transaction + start_transaction "container-install-$container_name" + + # Create base container image + if ! create_base_container_image "$base_image" "$container_name"; then + rollback_transaction + return 1 + fi + + # Run package installation in container + case "$CONTAINER_RUNTIME" in + podman) + if ! run_podman_install "$base_image" "$container_name" "$temp_dir" "${packages[@]}"; then + rollback_transaction + return 1 + fi + ;; + docker) + if ! run_docker_install "$base_image" "$container_name" "$temp_dir" "${packages[@]}"; then + rollback_transaction + return 1 + fi + ;; + systemd-nspawn) + if ! run_nspawn_install "$base_image" "$container_name" "$temp_dir" "${packages[@]}"; then + rollback_transaction + return 1 + fi + ;; + esac + + # Create ComposeFS layer from container changes + if ! create_composefs_layer "$temp_dir" "$new_image"; then + rollback_transaction + return 1 + fi + + # Commit transaction + commit_transaction + + # Cleanup + cleanup_container_artifacts "$container_name" "$temp_dir" + + log_success "Container-based package installation completed" "apt-layer" + return 0 +} + +# Podman-based package installation +run_podman_install() { + local base_image="$1" + local container_name="$2" + local temp_dir="$3" + shift 3 + local packages=("$@") + + log_info "Running podman-based installation" "apt-layer" + + # Create container from base image + local container_id + if [[ -d "$WORKSPACE/images/$base_image" ]]; then + # Use ComposeFS image as base + container_id=$(podman create --name "$container_name" \ + --mount type=bind,source="$WORKSPACE/images/$base_image",target=/ \ + --mount type=bind,source="$temp_dir",target=/output \ + ubuntu:24.04 /bin/bash) + else + # Use standard Ubuntu image + container_id=$(podman create --name "$container_name" \ + --mount type=bind,source="$temp_dir",target=/output \ + ubuntu:24.04 /bin/bash) + fi + + if [[ -z "$container_id" ]]; then + log_error "Failed to create podman container" "apt-layer" + return 1 + fi + + # Start container and install packages + if ! podman start "$container_name"; then + log_error "Failed to start podman container" "apt-layer" + podman rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Install packages + local install_cmd="apt-get update && apt-get install -y ${packages[*]} && apt-get clean" + if ! podman exec "$container_name" /bin/bash -c "$install_cmd"; then + log_error "Package installation failed in podman container" "apt-layer" + podman stop "$container_name" 2>/dev/null || true + podman rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Export container filesystem + if ! podman export "$container_name" | tar -x -C "$temp_dir"; then + log_error "Failed to export podman container filesystem" "apt-layer" + podman stop "$container_name" 2>/dev/null || true + podman rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Cleanup container + podman stop "$container_name" 2>/dev/null || true + podman rm "$container_name" 2>/dev/null || true + + log_success "Podman-based installation completed" "apt-layer" + return 0 +} + +# Docker-based package installation +run_docker_install() { + local base_image="$1" + local container_name="$2" + local temp_dir="$3" + shift 3 + local packages=("$@") + + log_info "Running docker-based installation" "apt-layer" + + # Create container from base image + local container_id + if [[ -d "$WORKSPACE/images/$base_image" ]]; then + # Use ComposeFS image as base + container_id=$(docker create --name "$container_name" \ + -v "$WORKSPACE/images/$base_image:/" \ + -v "$temp_dir:/output" \ + ubuntu:24.04 /bin/bash) + else + # Use standard Ubuntu image + container_id=$(docker create --name "$container_name" \ + -v "$temp_dir:/output" \ + ubuntu:24.04 /bin/bash) + fi + + if [[ -z "$container_id" ]]; then + log_error "Failed to create docker container" "apt-layer" + return 1 + fi + + # Start container and install packages + if ! docker start "$container_name"; then + log_error "Failed to start docker container" "apt-layer" + docker rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Install packages + local install_cmd="apt-get update && apt-get install -y ${packages[*]} && apt-get clean" + if ! docker exec "$container_name" /bin/bash -c "$install_cmd"; then + log_error "Package installation failed in docker container" "apt-layer" + docker stop "$container_name" 2>/dev/null || true + docker rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Export container filesystem + if ! docker export "$container_name" | tar -x -C "$temp_dir"; then + log_error "Failed to export docker container filesystem" "apt-layer" + docker stop "$container_name" 2>/dev/null || true + docker rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Cleanup container + docker stop "$container_name" 2>/dev/null || true + docker rm "$container_name" 2>/dev/null || true + + log_success "Docker-based installation completed" "apt-layer" + return 0 +} + +# systemd-nspawn-based package installation +run_nspawn_install() { + local base_image="$1" + local container_name="$2" + local temp_dir="$3" + shift 3 + local packages=("$@") + + log_info "Running systemd-nspawn-based installation" "apt-layer" + + # Create container directory + local container_dir="$temp_dir/container" + mkdir -p "$container_dir" + + # Set up base filesystem + if [[ -d "$WORKSPACE/images/$base_image" ]]; then + # Use ComposeFS image as base + log_info "Using ComposeFS image as base for nspawn" "apt-layer" + # Mount ComposeFS image and copy contents + local mount_point="$temp_dir/mount" + mkdir -p "$mount_point" + + if ! mount_composefs_image "$base_image" "$mount_point"; then + log_error "Failed to mount ComposeFS image for nspawn" "apt-layer" + return 1 + fi + + # Copy filesystem + if ! cp -a "$mount_point"/* "$container_dir/"; then + log_error "Failed to copy filesystem for nspawn" "apt-layer" + umount "$mount_point" 2>/dev/null || true + return 1 + fi + + umount "$mount_point" 2>/dev/null || true + else + # Use host filesystem as base + log_info "Using host filesystem as base for nspawn" "apt-layer" + # Create minimal container structure + mkdir -p "$container_dir"/{bin,lib,lib64,usr,etc,var} + # Copy essential files from host + cp -a /bin/bash "$container_dir/bin/" + cp -a /lib/x86_64-linux-gnu "$container_dir/lib/" + cp -a /usr/bin/apt-get "$container_dir/usr/bin/" + # Add minimal /etc structure + echo "deb http://archive.ubuntu.com/ubuntu/ jammy main" > "$container_dir/etc/apt/sources.list" + fi + + # Run package installation in nspawn container + local install_cmd="apt-get update && apt-get install -y ${packages[*]} && apt-get clean" + if ! systemd-nspawn -D "$container_dir" /bin/bash -c "$install_cmd"; then + log_error "Package installation failed in nspawn container" "apt-layer" + return 1 + fi + + # Move container contents to temp_dir + mv "$container_dir"/* "$temp_dir/" 2>/dev/null || true + + log_success "systemd-nspawn-based installation completed" "apt-layer" + return 0 +} + +# Create ComposeFS layer from container changes +create_composefs_layer() { + local temp_dir="$1" + local new_image="$2" + + log_info "Creating ComposeFS layer from container changes" "apt-layer" + + # Ensure new image directory exists + local image_dir="$WORKSPACE/images/$new_image" + mkdir -p "$image_dir" + + # Use ComposeFS backend to create layer + if ! "$COMPOSEFS_SCRIPT" create "$new_image" "$temp_dir"; then + log_error "Failed to create ComposeFS layer" "apt-layer" + return 1 + fi + + log_success "ComposeFS layer created: $new_image" "apt-layer" + return 0 +} + +# Cleanup container artifacts +cleanup_container_artifacts() { + local container_name="$1" + local temp_dir="$2" + + log_info "Cleaning up container artifacts" "apt-layer" + + # Remove temporary directory + if [[ -d "$temp_dir" ]]; then + rm -rf "$temp_dir" + fi + + # Cleanup any remaining containers (safety) + case "$CONTAINER_RUNTIME" in + podman) + podman rm "$container_name" 2>/dev/null || true + ;; + docker) + docker rm "$container_name" 2>/dev/null || true + ;; + esac + + log_success "Container artifacts cleaned up" "apt-layer" +} + +# Container-based layer removal +container_remove_layer() { + local image_name="$1" + + log_info "Removing container-based layer: $image_name" "apt-layer" + + # Use ComposeFS backend to remove layer + if ! "$COMPOSEFS_SCRIPT" remove "$image_name"; then + log_error "Failed to remove ComposeFS layer" "apt-layer" + return 1 + fi + + log_success "Container-based layer removed: $image_name" "apt-layer" + return 0 +} + +# Container-based layer listing +container_list_layers() { + log_info "Listing container-based layers" "apt-layer" + + # Use ComposeFS backend to list layers + if ! "$COMPOSEFS_SCRIPT" list-images; then + log_error "Failed to list ComposeFS layers" "apt-layer" + return 1 + fi + + return 0 +} + +# Container-based layer information +container_layer_info() { + local image_name="$1" + + log_info "Getting container-based layer info: $image_name" "apt-layer" + + # Use ComposeFS backend to get layer info + if ! "$COMPOSEFS_SCRIPT" info "$image_name"; then + log_error "Failed to get ComposeFS layer info" "apt-layer" + return 1 + fi + + return 0 +} + +# Container-based layer mounting +container_mount_layer() { + local image_name="$1" + local mount_point="$2" + + log_info "Mounting container-based layer: $image_name at $mount_point" "apt-layer" + + # Use ComposeFS backend to mount layer + if ! "$COMPOSEFS_SCRIPT" mount "$image_name" "$mount_point"; then + log_error "Failed to mount ComposeFS layer" "apt-layer" + return 1 + fi + + log_success "Container-based layer mounted: $image_name at $mount_point" "apt-layer" + return 0 +} + +# Container-based layer unmounting +container_unmount_layer() { + local mount_point="$1" + + log_info "Unmounting container-based layer at: $mount_point" "apt-layer" + + # Use ComposeFS backend to unmount layer + if ! "$COMPOSEFS_SCRIPT" unmount "$mount_point"; then + log_error "Failed to unmount ComposeFS layer" "apt-layer" + return 1 + fi + + log_success "Container-based layer unmounted: $mount_point" "apt-layer" + return 0 +} + +# Container runtime status check +container_status() { + log_info "Checking container runtime status" "apt-layer" + + echo "=== Container Runtime Status ===" + echo "Runtime: $CONTAINER_RUNTIME" + + case "$CONTAINER_RUNTIME" in + podman) + echo "Podman version: $(podman --version 2>/dev/null || echo 'Not available')" + echo "Podman info: $(podman info --format json 2>/dev/null | jq -r '.host.arch // "Unknown"' 2>/dev/null || echo 'Unknown')" + ;; + docker) + echo "Docker version: $(docker --version 2>/dev/null || echo 'Not available')" + echo "Docker info: $(docker info --format '{{.Architecture}}' 2>/dev/null || echo 'Unknown')" + ;; + systemd-nspawn) + echo "systemd-nspawn version: $(systemd-nspawn --version 2>/dev/null || echo 'Not available')" + ;; + esac + + echo "" + echo "=== ComposeFS Backend Status ===" + if [[ -f "$COMPOSEFS_SCRIPT" ]]; then + echo "ComposeFS script: $COMPOSEFS_SCRIPT" + echo "ComposeFS version: $("$COMPOSEFS_SCRIPT" --version 2>/dev/null || echo 'Version info not available')" + else + echo "ComposeFS script: Not found at $COMPOSEFS_SCRIPT" + fi + + echo "" + echo "=== Available Container Images ===" + container_list_layers +} + + + +# --- END OF SCRIPTLET: 04-container.sh --- + +# ============================================================================ +# Live Overlay System (rpm-ostree style) +# ============================================================================ + +# Ubuntu uBlue apt-layer Live Overlay System +# Implements live system layering similar to rpm-ostree +# Uses overlayfs for live package installation and management + +# ============================================================================= +# LIVE OVERLAY SYSTEM FUNCTIONS +# ============================================================================= + +# Live overlay state file (with fallbacks for when load_path_config() is not available) +# These will be overridden by load_path_config() when available +LIVE_OVERLAY_STATE_FILE="${LIVE_OVERLAY_STATE_FILE:-/var/lib/apt-layer/live-overlay.state}" +LIVE_OVERLAY_MOUNT_POINT="${LIVE_OVERLAY_MOUNT_POINT:-/var/lib/apt-layer/live-overlay/mount}" +LIVE_OVERLAY_PACKAGE_LOG="${LIVE_OVERLAY_PACKAGE_LOG:-/var/log/apt-layer/live-overlay-packages.log}" +LIVE_OVERLAY_DIR="${LIVE_OVERLAY_DIR:-/var/lib/apt-layer/live-overlay}" +LIVE_OVERLAY_UPPER_DIR="${LIVE_OVERLAY_UPPER_DIR:-/var/lib/apt-layer/live-overlay/upper}" +LIVE_OVERLAY_WORK_DIR="${LIVE_OVERLAY_WORK_DIR:-/var/lib/apt-layer/live-overlay/work}" + +# Initialize live overlay system +init_live_overlay_system() { + log_info "Initializing live overlay system" "apt-layer" + + # Load system paths if available + if command -v load_path_config >/dev/null 2>&1; then + load_path_config + fi + + # Create live overlay directories + mkdir -p "$LIVE_OVERLAY_DIR" "$LIVE_OVERLAY_UPPER_DIR" "$LIVE_OVERLAY_WORK_DIR" + mkdir -p "$LIVE_OVERLAY_MOUNT_POINT" + + # Set proper permissions (use sudo if needed) + if [[ $EUID -eq 0 ]]; then + # Running as root, use chmod directly + chmod 755 "$LIVE_OVERLAY_DIR" 2>/dev/null || true + chmod 700 "$LIVE_OVERLAY_UPPER_DIR" "$LIVE_OVERLAY_WORK_DIR" 2>/dev/null || true + else + # Running as regular user, use sudo + sudo chmod 755 "$LIVE_OVERLAY_DIR" 2>/dev/null || true + sudo chmod 700 "$LIVE_OVERLAY_UPPER_DIR" "$LIVE_OVERLAY_WORK_DIR" 2>/dev/null || true + fi + + # Initialize package log if it doesn't exist + if [[ ! -f "$LIVE_OVERLAY_PACKAGE_LOG" ]]; then + touch "$LIVE_OVERLAY_PACKAGE_LOG" + if [[ $EUID -eq 0 ]]; then + chmod 644 "$LIVE_OVERLAY_PACKAGE_LOG" 2>/dev/null || true + else + sudo chmod 644 "$LIVE_OVERLAY_PACKAGE_LOG" 2>/dev/null || true + fi + fi + + # Conditional DNS fix for chroot overlay (WSL, etc) + if [[ -d "$LIVE_OVERLAY_MOUNT_POINT" ]]; then + if ! chroot "$LIVE_OVERLAY_MOUNT_POINT" getent hosts archive.ubuntu.com >/dev/null 2>&1; then + log_warning "DNS resolution failed in overlay. Injecting public DNS servers..." "apt-layer" + # Backup original resolv.conf if present + if [[ -f "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf" ]]; then + cp "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf" "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf.aptlayerbak" + fi + echo "nameserver 8.8.8.8" > "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf" + echo "nameserver 1.1.1.1" >> "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf" + chmod 644 "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf" + touch "$LIVE_OVERLAY_MOUNT_POINT/.dns_fixed_by_apt_layer" + log_success "DNS configuration applied to overlay" "apt-layer" + else + log_info "DNS resolution in overlay is working. No changes made." "apt-layer" + fi + fi + + log_success "Live overlay system initialized" "apt-layer" +} + +# Check if live overlay is active +is_live_overlay_active() { + if [[ -f "$LIVE_OVERLAY_STATE_FILE" ]]; then + local state + state=$(cat "$LIVE_OVERLAY_STATE_FILE" 2>/dev/null || echo "") + [[ "$state" == "active" ]] + else + false + fi +} + +# Check if system supports live overlay +check_live_overlay_support() { + local errors=0 + local test_dir="/tmp/overlay-test-$$" + local test_lower="$test_dir/lower" + local test_upper="$test_dir/upper" + local test_work="$test_dir/work" + local test_mount="$test_dir/mount" + + # Check for overlay module + if ! modprobe -n overlay >/dev/null 2>&1; then + log_error "Overlay module not available" "apt-layer" + errors=$((errors + 1)) + fi + + # Create test directories + mkdir -p "$test_lower" "$test_upper" "$test_work" "$test_mount" 2>/dev/null + + # Check for overlayfs mount support + if ! mount -t overlay overlay -o "lowerdir=$test_lower,upperdir=$test_upper,workdir=$test_work" "$test_mount" 2>/dev/null; then + log_error "Overlayfs mount not supported" "apt-layer" + errors=$((errors + 1)) + else + umount "$test_mount" 2>/dev/null + fi + + # Cleanup test directories + rm -rf "$test_dir" 2>/dev/null + + # Check for read-only root filesystem + if ! is_root_readonly; then + log_warning "Root filesystem is not read-only - live overlay may not be necessary" "apt-layer" + fi + + if [[ $errors -gt 0 ]]; then + return 1 + fi + + return 0 +} + +# Check if root filesystem is read-only +is_root_readonly() { + local root_mount + root_mount=$(findmnt -n -o OPTIONS / | grep -o "ro" || echo "") + [[ -n "$root_mount" ]] +} + +# Start live overlay +start_live_overlay() { + log_info "Starting live overlay system" "apt-layer" + + # Check if already active + if is_live_overlay_active; then + log_warning "Live overlay is already active" "apt-layer" + return 0 + fi + + # Check system support + if ! check_live_overlay_support; then + log_error "System does not support live overlay" "apt-layer" + return 1 + fi + + # Initialize system + init_live_overlay_system + + # Create overlay mount + log_info "Creating overlay mount" "apt-layer" + if mount -t overlay overlay -o "lowerdir=/,upperdir=$LIVE_OVERLAY_UPPER_DIR,workdir=$LIVE_OVERLAY_WORK_DIR" "$LIVE_OVERLAY_MOUNT_POINT"; then + log_success "Overlay mount created successfully" "apt-layer" + + # Mark overlay as active + echo "active" > "$LIVE_OVERLAY_STATE_FILE" + + log_success "Live overlay started successfully" "apt-layer" + log_info "Changes will be applied to overlay and can be committed or rolled back" "apt-layer" + + return 0 + else + log_error "Failed to create overlay mount" "apt-layer" + return 1 + fi +} + +# Stop live overlay +stop_live_overlay() { + log_info "Stopping live overlay system" "apt-layer" + + # Check if overlay is active + if ! is_live_overlay_active; then + log_warning "Live overlay is not active" "apt-layer" + return 0 + fi + + # Check for active processes + if check_active_processes; then + log_warning "Active processes detected - overlay will persist until processes complete" "apt-layer" + return 0 + fi + + # Undo DNS fix if we applied it + if [[ -f "$LIVE_OVERLAY_MOUNT_POINT/.dns_fixed_by_apt_layer" ]]; then + if [[ -f "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf.aptlayerbak" ]]; then + mv "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf.aptlayerbak" "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf" + else + rm -f "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf" + fi + rm -f "$LIVE_OVERLAY_MOUNT_POINT/.dns_fixed_by_apt_layer" + log_info "DNS fix by apt-layer undone on overlay stop" "apt-layer" + fi + + # Unmount overlay + log_info "Unmounting overlay" "apt-layer" + if umount "$LIVE_OVERLAY_MOUNT_POINT"; then + log_success "Overlay unmounted successfully" "apt-layer" + + # Remove state file + rm -f "$LIVE_OVERLAY_STATE_FILE" + + log_success "Live overlay stopped successfully" "apt-layer" + return 0 + else + log_error "Failed to unmount overlay" "apt-layer" + return 1 + fi +} + +# Check for active processes that might prevent unmounting +check_active_processes() { + # Check for package manager processes + if pgrep -f "apt|dpkg|apt-get" >/dev/null 2>&1; then + return 0 + fi + + # Check for processes using the overlay mount + if lsof "$LIVE_OVERLAY_MOUNT_POINT" >/dev/null 2>&1; then + return 0 + fi + + return 1 +} + +# Get live overlay status +get_live_overlay_status() { + echo "=== Live Overlay Status ===" + + if is_live_overlay_active; then + log_success "� Live overlay is ACTIVE" "apt-layer" + + # Show mount details + if mountpoint -q "$LIVE_OVERLAY_MOUNT_POINT"; then + log_info "Overlay mount point: $LIVE_OVERLAY_MOUNT_POINT" "apt-layer" + + # Show overlay usage + if [[ -d "$LIVE_OVERLAY_UPPER_DIR" ]]; then + local usage=$(du -sh "$LIVE_OVERLAY_UPPER_DIR" 2>/dev/null | cut -f1 || echo "unknown") + log_info "Overlay usage: $usage" "apt-layer" + fi + + # Show installed packages + if [[ -f "$LIVE_OVERLAY_PACKAGE_LOG" ]]; then + local package_count=$(wc -l < "$LIVE_OVERLAY_PACKAGE_LOG" 2>/dev/null || echo "0") + log_info "Packages installed in overlay: $package_count" "apt-layer" + fi + else + log_warning "�� Overlay mount point not mounted" "apt-layer" + fi + + # Check for active processes + if check_active_processes; then + log_warning "�� Active processes detected - overlay cannot be stopped" "apt-layer" + fi + else + log_info "� Live overlay is not active" "apt-layer" + + # Check if system supports live overlay + if check_live_overlay_support >/dev/null 2>&1; then + log_info "� System supports live overlay" "apt-layer" + log_info "Use '--live-overlay start' to start live overlay" "apt-layer" + else + log_warning "�� System does not support live overlay" "apt-layer" + fi + fi + + echo "" +} + +# Install packages in live overlay +live_install() { + local packages=("$@") + + log_info "Installing packages in live overlay: ${packages[*]}" "apt-layer" + + # Check if overlay is active + if ! is_live_overlay_active; then + log_error "Live overlay is not active" "apt-layer" + log_info "Use '--live-overlay start' to start live overlay first" "apt-layer" + return 1 + fi + + # Check for root privileges + if [[ $EUID -ne 0 ]]; then + log_error "Root privileges required for live installation" "apt-layer" + return 1 + fi + + # Update package lists in overlay + log_info "Updating package lists in overlay" "apt-layer" + if ! chroot "$LIVE_OVERLAY_MOUNT_POINT" apt-get update; then + log_error "Failed to update package lists" "apt-layer" + log_warning "Network or DNS error? For offline or WSL overlays, use: apt-layer --live-dpkg <.deb files>" "apt-layer" + return 1 + fi + + # Install packages in overlay + log_info "Installing packages in overlay" "apt-layer" + if chroot "$LIVE_OVERLAY_MOUNT_POINT" apt-get install -y "${packages[@]}"; then + log_success "Packages installed successfully in overlay" "apt-layer" + + # Log installed packages + for package in "${packages[@]}"; do + echo "$(date '+%Y-%m-%d %H:%M:%S') - INSTALLED: $package" >> "$LIVE_OVERLAY_PACKAGE_LOG" + done + + log_info "Changes are applied to overlay and can be committed or rolled back" "apt-layer" + return 0 + else + log_error "Failed to install packages in overlay" "apt-layer" + log_warning "If this is a network or DNS issue, try: apt-layer --live-dpkg <.deb files>" "apt-layer" + return 1 + fi +} + +# Manage live overlay +manage_live_overlay() { + local action="$1" + shift + local options=("$@") + + case "$action" in + "start") + start_live_overlay + ;; + "stop") + stop_live_overlay + ;; + "status") + get_live_overlay_status + ;; + "commit") + local message="${options[0]:-Live overlay changes}" + commit_live_overlay "$message" + ;; + "rollback") + rollback_live_overlay + ;; + "list") + list_live_overlay_packages + ;; + "clean") + clean_live_overlay + ;; + *) + log_error "Unknown live overlay action: $action" "apt-layer" + log_info "Valid actions: start, stop, status, commit, rollback, list, clean" "apt-layer" + return 1 + ;; + esac +} + +# Commit live overlay changes +commit_live_overlay() { + local message="$1" + + log_info "Committing live overlay changes: $message" "apt-layer" + + # Check if overlay is active + if ! is_live_overlay_active; then + log_error "Live overlay is not active" "apt-layer" + return 1 + fi + + # Check if there are changes to commit + if ! has_overlay_changes; then + log_warning "No changes to commit" "apt-layer" + return 0 + fi + + # Create new ComposeFS layer from overlay changes + local timestamp=$(date '+%Y%m%d_%H%M%S') + local layer_name="live-overlay-commit-${timestamp}" + + log_info "Creating new layer: $layer_name" "apt-layer" + + # Create layer from overlay changes + if create_layer_from_overlay "$layer_name" "$message"; then + log_success "Live overlay changes committed as layer: $layer_name" "apt-layer" + + # Clean up overlay + clean_live_overlay + + return 0 + else + log_error "Failed to commit live overlay changes" "apt-layer" + return 1 + fi +} + +# Check if overlay has changes +has_overlay_changes() { + if [[ -d "$LIVE_OVERLAY_UPPER_DIR" ]]; then + # Check if upper directory has any content + if [[ -n "$(find "$LIVE_OVERLAY_UPPER_DIR" -mindepth 1 -maxdepth 1 2>/dev/null)" ]]; then + return 0 + fi + fi + + return 1 +} + +# Create layer from overlay changes +create_layer_from_overlay() { + local layer_name="$1" + local message="$2" + + # Create temporary directory for layer + local temp_layer_dir="${TEMP_DIR:-/tmp/apt-layer}/live-layer-${layer_name}" + mkdir -p "$temp_layer_dir" + + # Copy overlay changes to temporary directory + log_info "Copying overlay changes to temporary layer" "apt-layer" + if ! cp -a "$LIVE_OVERLAY_UPPER_DIR"/* "$temp_layer_dir/" 2>/dev/null; then + log_error "Failed to copy overlay changes" "apt-layer" + rm -rf "$temp_layer_dir" + return 1 + fi + + # Create ComposeFS layer + log_info "Creating ComposeFS layer from overlay changes" "apt-layer" + if ! create_composefs_layer "$temp_layer_dir" "$layer_name" "$message"; then + log_error "Failed to create ComposeFS layer" "apt-layer" + rm -rf "$temp_layer_dir" + return 1 + fi + + # Clean up temporary directory + rm -rf "$temp_layer_dir" + + return 0 +} + +# Create ComposeFS layer from directory +create_composefs_layer() { + local source_dir="$1" + local layer_name="$2" + local message="$3" + + # Use composefs-alternative to create layer + if command -v composefs-alternative >/dev/null 2>&1; then + if composefs-alternative create-layer "$source_dir" "$layer_name" "$message"; then + return 0 + fi + fi + + # Fallback: create simple squashfs layer + local layer_file="${BUILD_DIR:-/var/lib/apt-layer/build}/${layer_name}.squashfs" + mkdir -p "$(dirname "$layer_file")" + + if mksquashfs "$source_dir" "$layer_file" -comp "${SQUASHFS_COMPRESSION:-xz}" -b "${SQUASHFS_BLOCK_SIZE:-1M}"; then + log_success "Created squashfs layer: $layer_file" "apt-layer" + return 0 + else + log_error "Failed to create squashfs layer" "apt-layer" + return 1 + fi +} + +# Rollback live overlay changes +rollback_live_overlay() { + log_info "Rolling back live overlay changes" "apt-layer" + + # Check if overlay is active + if ! is_live_overlay_active; then + log_error "Live overlay is not active" "apt-layer" + return 1 + fi + + # Stop overlay (this will discard changes) + if stop_live_overlay; then + log_success "Live overlay changes rolled back successfully" "apt-layer" + return 0 + else + log_error "Failed to rollback live overlay changes" "apt-layer" + return 1 + fi +} + +# List packages installed in live overlay +list_live_overlay_packages() { + log_info "Listing packages installed in live overlay" "apt-layer" + + if [[ -f "$LIVE_OVERLAY_PACKAGE_LOG" ]]; then + if [[ -s "$LIVE_OVERLAY_PACKAGE_LOG" ]]; then + echo "=== Packages Installed in Live Overlay ===" + cat "$LIVE_OVERLAY_PACKAGE_LOG" + echo "" + else + log_info "No packages installed in live overlay" "apt-layer" + fi + else + log_info "No package log found" "apt-layer" + fi +} + +# Clean live overlay +clean_live_overlay() { + log_info "Cleaning live overlay" "apt-layer" + + # Stop overlay if active + if is_live_overlay_active; then + stop_live_overlay + fi + + # Clean up overlay directories + rm -rf "$LIVE_OVERLAY_UPPER_DIR"/* "$LIVE_OVERLAY_WORK_DIR"/* 2>/dev/null + + # Clean up package log + rm -f "$LIVE_OVERLAY_PACKAGE_LOG" + + # Remove state file + rm -f "$LIVE_OVERLAY_STATE_FILE" + + log_success "Live overlay cleaned successfully" "apt-layer" +} + +# ============================================================================= +# INTEGRATION FUNCTIONS +# ============================================================================= + +# Initialize live overlay system on script startup +init_live_overlay_on_startup() { + # Only initialize if not already done + if [[ ! -d "$LIVE_OVERLAY_DIR" ]]; then + init_live_overlay_system + fi +} + +# Cleanup live overlay on script exit +cleanup_live_overlay_on_exit() { + # Only cleanup if overlay is active and no processes are using it + if is_live_overlay_active && ! check_active_processes; then + log_info "Cleaning up live overlay on exit" "apt-layer" + stop_live_overlay + fi +} + +# Register cleanup function +trap cleanup_live_overlay_on_exit EXIT + +# --- END OF SCRIPTLET: 05-live-overlay.sh --- + +# ============================================================================ +# OCI Export/Import Integration +# ============================================================================ +# OCI Integration for Particle-OS apt-layer Tool +# Provides ComposeFS OCI export/import functionality for container-based layer creation + +# OCI registry configuration +declare -A OCI_REGISTRY_CONFIG +OCI_REGISTRY_CONFIG["default_registry"]="docker.io" +OCI_REGISTRY_CONFIG["auth_file"]="$HOME/.docker/config.json" +OCI_REGISTRY_CONFIG["insecure_registries"]="" +OCI_REGISTRY_CONFIG["registry_mirrors"]="" + +# OCI image format validation +validate_oci_image_name() { + local image_name="$1" + + log_debug "Validating OCI image name: $image_name" "apt-layer" + + # Check for empty name + if [[ -z "$image_name" ]]; then + log_error "Empty OCI image name provided" "apt-layer" + return 1 + fi + + # Validate OCI image name format (registry/repository:tag) + if [[ ! "$image_name" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*/[a-zA-Z0-9][a-zA-Z0-9._-]*(:[a-zA-Z0-9._-]*)?$ ]] && \ + [[ ! "$image_name" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*(:[a-zA-Z0-9._-]*)?$ ]]; then + log_error "Invalid OCI image name format: $image_name" "apt-layer" + log_error "Expected format: [registry/]repository[:tag]" "apt-layer" + return 1 + fi + + log_success "OCI image name validated: $image_name" "apt-layer" + return 0 +} + +# Initialize OCI integration system +init_oci_system() { + log_info "Initializing OCI integration system" "apt-layer" + + # Ensure OCI workspace directories exist + local oci_workspace="${OCI_WORKSPACE_DIR:-$WORKSPACE/oci}" + local oci_temp="${OCI_TEMP_DIR:-$oci_workspace/temp}" + local oci_cache="${OCI_CACHE_DIR:-$oci_workspace/cache}" + local oci_export="${OCI_EXPORT_DIR:-$oci_workspace/export}" + local oci_import="${OCI_IMPORT_DIR:-$oci_workspace/import}" + + mkdir -p "$oci_workspace" + mkdir -p "$oci_temp" + mkdir -p "$oci_cache" + mkdir -p "$oci_export" + mkdir -p "$oci_import" + + # Check for OCI tools + local missing_tools=() + + # Check for skopeo (preferred for OCI operations) + if ! command -v skopeo &> /dev/null; then + missing_tools+=("skopeo") + fi + + # Check for podman (fallback for OCI operations) + if ! command -v podman &> /dev/null; then + missing_tools+=("podman") + fi + + # Check for docker (alternative fallback) + if ! command -v docker &> /dev/null; then + missing_tools+=("docker") + fi + + if [[ ${#missing_tools[@]} -eq 3 ]]; then + log_error "No OCI tools found (skopeo, podman, or docker required)" "apt-layer" + return 1 + fi + + # Set preferred OCI tool + if command -v skopeo &> /dev/null; then + OCI_TOOL="skopeo" + log_info "Using skopeo for OCI operations" "apt-layer" + elif command -v podman &> /dev/null; then + OCI_TOOL="podman" + log_info "Using podman for OCI operations" "apt-layer" + else + OCI_TOOL="docker" + log_info "Using docker for OCI operations" "apt-layer" + fi + + log_success "OCI integration system initialized with $OCI_TOOL" "apt-layer" + return 0 +} + +# Export ComposeFS image to OCI format +export_oci_image() { + local composefs_image="$1" + local oci_image_name="$2" + local temp_dir="${3:-$WORKSPACE/oci/export/$(date +%s)-$$}" + + log_info "Exporting ComposeFS image to OCI: $composefs_image -> $oci_image_name" "apt-layer" + + # Validate inputs + if [[ -z "$composefs_image" ]] || [[ -z "$oci_image_name" ]]; then + log_error "Missing required arguments for export_oci_image" "apt-layer" + return 1 + fi + + if ! validate_oci_image_name "$oci_image_name"; then + return 1 + fi + + # Check if ComposeFS image exists + if ! "$COMPOSEFS_SCRIPT" info "$composefs_image" >/dev/null 2>&1; then + log_error "ComposeFS image not found: $composefs_image" "apt-layer" + return 1 + fi + + # Create temporary directory + mkdir -p "$temp_dir" + local cleanup_temp=1 + + # Start transaction + start_transaction "export-oci-$composefs_image" + + # Mount ComposeFS image + local mount_point="$temp_dir/mount" + mkdir -p "$mount_point" + + update_transaction_phase "mounting_composefs_image" + if ! "$COMPOSEFS_SCRIPT" mount "$composefs_image" "$mount_point"; then + log_error "Failed to mount ComposeFS image: $composefs_image" "apt-layer" + rollback_transaction + return 1 + fi + + # Create OCI image structure + local oci_dir="$temp_dir/oci" + mkdir -p "$oci_dir" + + update_transaction_phase "creating_oci_structure" + if ! create_oci_image_structure "$mount_point" "$oci_dir" "$oci_image_name"; then + log_error "Failed to create OCI image structure" "apt-layer" + rollback_transaction + return 1 + fi + + # Push OCI image to registry + update_transaction_phase "pushing_oci_image" + if ! push_oci_image "$oci_dir" "$oci_image_name"; then + log_error "Failed to push OCI image: $oci_image_name" "apt-layer" + rollback_transaction + return 1 + fi + + # Unmount ComposeFS image + "$COMPOSEFS_SCRIPT" unmount "$mount_point" 2>/dev/null || true + + commit_transaction + log_success "ComposeFS image exported to OCI: $oci_image_name" "apt-layer" + + # Cleanup + if [[ $cleanup_temp -eq 1 ]]; then + rm -rf "$temp_dir" + fi + + return 0 +} + +# Create OCI image structure from filesystem +create_oci_image_structure() { + local source_dir="$1" + local oci_dir="$2" + local image_name="$3" + + log_debug "Creating OCI image structure from: $source_dir" "apt-layer" + + # Create OCI directory structure + mkdir -p "$oci_dir"/{blobs,refs} + + # Create manifest + local manifest_file="$oci_dir/manifest.json" + local config_file="$oci_dir/config.json" + + # Generate image configuration + cat > "$config_file" << EOF +{ + "architecture": "amd64", + "config": { + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": null, + "Image": "", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": null, + "OnBuild": null, + "Labels": { + "org.opencontainers.image.title": "$image_name", + "org.opencontainers.image.description": "Exported from ComposeFS image", + "org.opencontainers.image.created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + } + }, + "container": "", + "container_config": { + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": null, + "Image": "", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": null, + "OnBuild": null, + "Labels": null + }, + "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "docker_version": "20.10.0", + "history": [ + { + "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "created_by": "apt-layer export_oci_image", + "comment": "Exported from ComposeFS image" + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [] + } +} +EOF + + # Create layer from source directory + local layer_file="$oci_dir/layer.tar" + if ! tar -cf "$layer_file" -C "$source_dir" .; then + log_error "Failed to create layer tarball" "apt-layer" + return 1 + fi + + # Calculate layer digest + local layer_digest + layer_digest=$(sha256sum "$layer_file" | cut -d' ' -f1) + local layer_blob="$oci_dir/blobs/sha256/$layer_digest" + + # Move layer to blobs directory + mkdir -p "$(dirname "$layer_blob")" + mv "$layer_file" "$layer_blob" + + # Update config with layer diff_id + local diff_id="sha256:$layer_digest" + jq ".rootfs.diff_ids = [\"$diff_id\"]" "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file" + + # Calculate config digest + local config_digest + config_digest=$(sha256sum "$config_file" | cut -d' ' -f1) + local config_blob="$oci_dir/blobs/sha256/$config_digest" + + # Move config to blobs directory + mkdir -p "$(dirname "$config_blob")" + mv "$config_file" "$config_blob" + + # Create manifest + cat > "$manifest_file" << EOF +[ + { + "Config": "blobs/sha256/$config_digest", + "RepoTags": ["$image_name"], + "Layers": ["blobs/sha256/$layer_digest"] + } +] +EOF + + log_success "OCI image structure created" "apt-layer" + return 0 +} + +# Push OCI image to registry +push_oci_image() { + local oci_dir="$1" + local image_name="$2" + + log_debug "Pushing OCI image: $image_name" "apt-layer" + + case "$OCI_TOOL" in + skopeo) + if ! skopeo copy "dir:$oci_dir" "docker://$image_name"; then + log_error "Failed to push image with skopeo" "apt-layer" + return 1 + fi + ;; + podman) + if ! podman load -i "$oci_dir/manifest.json" && \ + ! podman tag "$(podman images --format '{{.ID}}' | head -1)" "$image_name" && \ + ! podman push "$image_name"; then + log_error "Failed to push image with podman" "apt-layer" + return 1 + fi + ;; + docker) + if ! docker load -i "$oci_dir/manifest.json" && \ + ! docker tag "$(docker images --format '{{.ID}}' | head -1)" "$image_name" && \ + ! docker push "$image_name"; then + log_error "Failed to push image with docker" "apt-layer" + return 1 + fi + ;; + esac + + log_success "OCI image pushed: $image_name" "apt-layer" + return 0 +} + +# Import OCI image as ComposeFS image +import_oci_image() { + local oci_image_name="$1" + local composefs_image="$2" + local temp_dir="${3:-$WORKSPACE/oci/import/$(date +%s)-$$}" + + log_info "Importing OCI image as ComposeFS: $oci_image_name -> $composefs_image" "apt-layer" + + # Validate inputs + if [[ -z "$oci_image_name" ]] || [[ -z "$composefs_image" ]]; then + log_error "Missing required arguments for import_oci_image" "apt-layer" + return 1 + fi + + if ! validate_oci_image_name "$oci_image_name"; then + return 1 + fi + + # Create temporary directory + mkdir -p "$temp_dir" + local cleanup_temp=1 + + # Start transaction + start_transaction "import-oci-$oci_image_name" + + # Pull OCI image + update_transaction_phase "pulling_oci_image" + if ! pull_oci_image "$oci_image_name" "$temp_dir"; then + log_error "Failed to pull OCI image: $oci_image_name" "apt-layer" + rollback_transaction + return 1 + fi + + # Extract image filesystem + update_transaction_phase "extracting_image_filesystem" + local rootfs_dir="$temp_dir/rootfs" + if ! extract_oci_filesystem "$temp_dir" "$rootfs_dir"; then + log_error "Failed to extract OCI filesystem" "apt-layer" + rollback_transaction + return 1 + fi + + # Create ComposeFS image from extracted filesystem + update_transaction_phase "creating_composefs_image" + if ! "$COMPOSEFS_SCRIPT" create "$composefs_image" "$rootfs_dir"; then + log_error "Failed to create ComposeFS image: $composefs_image" "apt-layer" + rollback_transaction + return 1 + fi + + commit_transaction + log_success "OCI image imported as ComposeFS: $composefs_image" "apt-layer" + + # Cleanup + if [[ $cleanup_temp -eq 1 ]]; then + rm -rf "$temp_dir" + fi + + return 0 +} + +# Pull OCI image from registry +pull_oci_image() { + local image_name="$1" + local temp_dir="$2" + + log_debug "Pulling OCI image: $image_name" "apt-layer" + + case "$OCI_TOOL" in + skopeo) + if ! skopeo copy "docker://$image_name" "dir:$temp_dir"; then + log_error "Failed to pull image with skopeo" "apt-layer" + return 1 + fi + ;; + podman) + if ! podman pull "$image_name" && \ + ! podman save "$image_name" -o "$temp_dir/image.tar"; then + log_error "Failed to pull image with podman" "apt-layer" + return 1 + fi + ;; + docker) + if ! docker pull "$image_name" && \ + ! docker save "$image_name" -o "$temp_dir/image.tar"; then + log_error "Failed to pull image with docker" "apt-layer" + return 1 + fi + ;; + esac + + log_success "OCI image pulled: $image_name" "apt-layer" + return 0 +} + +# Extract filesystem from OCI image +extract_oci_filesystem() { + local oci_dir="$1" + local rootfs_dir="$2" + + log_debug "Extracting OCI filesystem to: $rootfs_dir" "apt-layer" + + mkdir -p "$rootfs_dir" + + # Handle different OCI tool outputs + if [[ -f "$oci_dir/manifest.json" ]]; then + # skopeo output + local layer_file + layer_file=$(jq -r '.[0].Layers[0]' "$oci_dir/manifest.json") + if [[ -f "$oci_dir/$layer_file" ]]; then + tar -xf "$oci_dir/$layer_file" -C "$rootfs_dir" + else + log_error "Layer file not found: $oci_dir/$layer_file" "apt-layer" + return 1 + fi + elif [[ -f "$oci_dir/image.tar" ]]; then + # podman/docker output + tar -xf "$oci_dir/image.tar" -C "$rootfs_dir" + # Find and extract the layer + local layer_file + layer_file=$(find "$rootfs_dir" -name "*.tar" | head -1) + if [[ -n "$layer_file" ]]; then + mkdir -p "$rootfs_dir.tmp" + tar -xf "$layer_file" -C "$rootfs_dir.tmp" + mv "$rootfs_dir.tmp"/* "$rootfs_dir/" + rmdir "$rootfs_dir.tmp" + fi + else + log_error "No valid OCI image structure found" "apt-layer" + return 1 + fi + + log_success "OCI filesystem extracted" "apt-layer" + return 0 +} + +# List available OCI images +list_oci_images() { + log_info "Listing available OCI images" "apt-layer" + + case "$OCI_TOOL" in + skopeo) + # skopeo doesn't have a direct list command, use registry API + log_warning "OCI image listing not fully supported with skopeo" "apt-layer" + ;; + podman) + podman images --format "table {{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}\t{{.Size}}" + ;; + docker) + docker images --format "table {{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}\t{{.Size}}" + ;; + esac +} + +# Get OCI image information +get_oci_image_info() { + local image_name="$1" + + log_info "Getting OCI image info: $image_name" "apt-layer" + + if ! validate_oci_image_name "$image_name"; then + return 1 + fi + + case "$OCI_TOOL" in + skopeo) + skopeo inspect "docker://$image_name" + ;; + podman) + podman inspect "$image_name" + ;; + docker) + docker inspect "$image_name" + ;; + esac +} + +# Remove OCI image +remove_oci_image() { + local image_name="$1" + + log_info "Removing OCI image: $image_name" "apt-layer" + + if ! validate_oci_image_name "$image_name"; then + return 1 + fi + + case "$OCI_TOOL" in + skopeo) + log_warning "Image removal not supported with skopeo" "apt-layer" + return 1 + ;; + podman) + if ! podman rmi "$image_name"; then + log_error "Failed to remove image with podman" "apt-layer" + return 1 + fi + ;; + docker) + if ! docker rmi "$image_name"; then + log_error "Failed to remove image with docker" "apt-layer" + return 1 + fi + ;; + esac + + log_success "OCI image removed: $image_name" "apt-layer" + return 0 +} + +# OCI system status +oci_status() { + log_info "OCI Integration System Status" "apt-layer" + + echo "=== OCI Tool Configuration ===" + echo "Preferred tool: $OCI_TOOL" + echo "Available tools:" + command -v skopeo &> /dev/null && echo " skopeo" + command -v podman &> /dev/null && echo " podman" + command -v docker &> /dev/null && echo " docker" + + echo "" + echo "=== OCI Workspace ===" + echo "OCI directory: ${OCI_WORKSPACE_DIR:-$WORKSPACE/oci}" + echo "Export directory: ${OCI_EXPORT_DIR:-$WORKSPACE/oci/export}" + echo "Import directory: ${OCI_IMPORT_DIR:-$WORKSPACE/oci/import}" + echo "Cache directory: ${OCI_CACHE_DIR:-$WORKSPACE/oci/cache}" + + echo "" + echo "=== ComposeFS Backend ===" + if [[ -f "$COMPOSEFS_SCRIPT" ]]; then + echo "ComposeFS script: $COMPOSEFS_SCRIPT" + echo "ComposeFS version: $("$COMPOSEFS_SCRIPT" --version 2>/dev/null || echo 'Version info not available')" + else + echo "ComposeFS script: Not found at $COMPOSEFS_SCRIPT" + fi + + echo "" + echo "=== Available OCI Images ===" + list_oci_images +} + +# --- END OF SCRIPTLET: 06-oci-integration.sh --- + +# ============================================================================ +# Bootloader Integration (UEFI/GRUB/systemd-boot) +# ============================================================================ + +# Ubuntu uBlue apt-layer Bootloader Integration +# Provides comprehensive bootloader management for immutable deployments +# Supports UEFI, GRUB, systemd-boot, and kernel argument management + +# ============================================================================= +# BOOTLOADER SYSTEM FUNCTIONS +# ============================================================================= + +# Bootloader configuration (with fallbacks for when particle-config.sh is not loaded) +BOOTLOADER_CONFIG_DIR="${UBLUE_CONFIG_DIR:-/etc/particle-os}/bootloader" +BOOTLOADER_STATE_DIR="${UBLUE_ROOT:-/var/lib/particle-os}/bootloader" +BOOTLOADER_ENTRIES_DIR="$BOOTLOADER_STATE_DIR/entries" +BOOTLOADER_BACKUP_DIR="$BOOTLOADER_STATE_DIR/backups" +KARGS_CONFIG_DIR="${UBLUE_CONFIG_DIR:-/etc/particle-os}/kargs" +KARGS_STATE_FILE="$BOOTLOADER_STATE_DIR/kargs.json" + +# Initialize bootloader system +init_bootloader_system() { + log_info "Initializing bootloader system" "apt-layer" + + # Create bootloader directories + mkdir -p "$BOOTLOADER_CONFIG_DIR" "$BOOTLOADER_STATE_DIR" "$BOOTLOADER_ENTRIES_DIR" "$BOOTLOADER_BACKUP_DIR" + mkdir -p "$KARGS_CONFIG_DIR" + + # Set proper permissions + chmod 755 "$BOOTLOADER_CONFIG_DIR" "$BOOTLOADER_STATE_DIR" + chmod 700 "$BOOTLOADER_ENTRIES_DIR" "$BOOTLOADER_BACKUP_DIR" + + # Initialize kernel arguments state if it doesn't exist + if [[ ! -f "$KARGS_STATE_FILE" ]]; then + echo '{"current": [], "pending": [], "history": []}' > "$KARGS_STATE_FILE" + chmod 644 "$KARGS_STATE_FILE" + fi + + log_success "Bootloader system initialized" "apt-layer" +} + +# Detect bootloader type +detect_bootloader_type() { + log_debug "Detecting bootloader type" "apt-layer" + + # Check for UEFI + if [[ -d "/sys/firmware/efi" ]]; then + log_info "UEFI system detected" "apt-layer" + + # Check for systemd-boot (preferred for UEFI) + if command -v bootctl &>/dev/null && [[ -d "/boot/loader" ]]; then + echo "systemd-boot" + return 0 + fi + + # Check for GRUB UEFI + if command -v grub-install &>/dev/null && [[ -f "/boot/grub/grub.cfg" ]]; then + echo "grub-uefi" + return 0 + fi + + # Generic UEFI + echo "uefi" + return 0 + fi + + # Check for legacy BIOS bootloaders + if command -v grub-install &>/dev/null && [[ -f "/boot/grub/grub.cfg" ]]; then + echo "grub-legacy" + return 0 + fi + + if command -v lilo &>/dev/null; then + echo "lilo" + return 0 + fi + + if command -v syslinux &>/dev/null; then + echo "syslinux" + return 0 + fi + + log_warning "No supported bootloader detected" "apt-layer" + echo "unknown" + return 1 +} + +# Check if secure boot is enabled +is_secure_boot_enabled() { + if [[ -d "/sys/firmware/efi" ]]; then + if command -v mokutil &>/dev/null; then + if mokutil --sb-state 2>/dev/null | grep -q "SecureBoot enabled"; then + return 0 + fi + fi + + # Alternative check via efivar + if [[ -f "/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c" ]]; then + local secure_boot_value + secure_boot_value=$(od -An -tu1 /sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c 2>/dev/null | tr -d ' ' | tail -c1) + if [[ "$secure_boot_value" == "1" ]]; then + return 0 + fi + fi + fi + + return 1 +} + +# Get current kernel arguments +get_current_kernel_args() { + local kernel_args + kernel_args=$(cat /proc/cmdline 2>/dev/null || echo "") + echo "$kernel_args" +} + +# Parse kernel arguments into array +parse_kernel_args() { + local cmdline="$1" + local args=() + + # Split cmdline into individual arguments + while IFS= read -r -d '' arg; do + if [[ -n "$arg" ]]; then + args+=("$arg") + fi + done < <(echo -n "$cmdline" | tr ' ' '\0') + + echo "${args[@]}" +} + +# Add kernel argument +add_kernel_arg() { + local arg="$1" + + if [[ -z "$arg" ]]; then + log_error "No kernel argument provided" "apt-layer" + return 1 + fi + + log_info "Adding kernel argument: $arg" "apt-layer" + + # Read current kernel arguments state + local current_args + current_args=$(jq -r '.current[]?' "$KARGS_STATE_FILE" 2>/dev/null || echo "") + + # Check if argument already exists + if echo "$current_args" | grep -q "^$arg$"; then + log_warning "Kernel argument already exists: $arg" "apt-layer" + return 0 + fi + + # Add to pending arguments + local pending_args + pending_args=$(jq -r '.pending[]?' "$KARGS_STATE_FILE" 2>/dev/null || echo "") + + if echo "$pending_args" | grep -q "^$arg$"; then + log_warning "Kernel argument already pending: $arg" "apt-layer" + return 0 + fi + + # Update state file + jq --arg arg "$arg" '.pending += [$arg]' "$KARGS_STATE_FILE" > "$KARGS_STATE_FILE.tmp" && \ + mv "$KARGS_STATE_FILE.tmp" "$KARGS_STATE_FILE" + + log_success "Kernel argument added to pending: $arg" "apt-layer" + return 0 +} + +# Remove kernel argument +remove_kernel_arg() { + local arg="$1" + + if [[ -z "$arg" ]]; then + log_error "No kernel argument provided" "apt-layer" + return 1 + fi + + log_info "Removing kernel argument: $arg" "apt-layer" + + # Remove from pending arguments + jq --arg arg "$arg" '(.pending | map(select(. != $arg))) as $new_pending | .pending = $new_pending' "$KARGS_STATE_FILE" > "$KARGS_STATE_FILE.tmp" && \ + mv "$KARGS_STATE_FILE.tmp" "$KARGS_STATE_FILE" + + log_success "Kernel argument removed from pending: $arg" "apt-layer" + return 0 +} + +# List kernel arguments +list_kernel_args() { + log_info "Listing kernel arguments" "apt-layer" + + echo "=== Current Kernel Arguments ===" + local current_args + current_args=$(get_current_kernel_args) + if [[ -n "$current_args" ]]; then + echo "$current_args" | tr ' ' '\n' | while read -r arg; do + if [[ -n "$arg" ]]; then + echo " $arg" + fi + done + else + log_info "No current kernel arguments found" "apt-layer" + fi + + echo "" + echo "=== Pending Kernel Arguments ===" + local pending_args + pending_args=$(jq -r '.pending[]?' "$KARGS_STATE_FILE" 2>/dev/null || echo "") + if [[ -n "$pending_args" ]]; then + echo "$pending_args" | while read -r arg; do + if [[ -n "$arg" ]]; then + echo " $arg (pending)" + fi + done + else + log_info "No pending kernel arguments" "apt-layer" + fi + + echo "" +} + +# Clear pending kernel arguments +clear_pending_kargs() { + log_info "Clearing pending kernel arguments" "apt-layer" + + jq '.pending = []' "$KARGS_STATE_FILE" > "$KARGS_STATE_FILE.tmp" && \ + mv "$KARGS_STATE_FILE.tmp" "$KARGS_STATE_FILE" + + log_success "Pending kernel arguments cleared" "apt-layer" +} + +# Apply kernel arguments to deployment +apply_kernel_args_to_deployment() { + local deployment_id="$1" + + if [[ -z "$deployment_id" ]]; then + log_error "No deployment ID provided" "apt-layer" + return 1 + fi + + log_info "Applying kernel arguments to deployment: $deployment_id" "apt-layer" + + # Get pending kernel arguments + local pending_args + pending_args=$(jq -r '.pending[]?' "$KARGS_STATE_FILE" 2>/dev/null || echo "") + + if [[ -z "$pending_args" ]]; then + log_info "No pending kernel arguments to apply" "apt-layer" + return 0 + fi + + # Create kernel arguments configuration for deployment + local kargs_config="$BOOTLOADER_ENTRIES_DIR/${deployment_id}.kargs" + echo "# Kernel arguments for deployment: $deployment_id" > "$kargs_config" + echo "# Generated on: $(date)" >> "$kargs_config" + echo "" >> "$kargs_config" + + echo "$pending_args" | while read -r arg; do + if [[ -n "$arg" ]]; then + echo "$arg" >> "$kargs_config" + fi + done + + # Move pending arguments to current and clear pending + local current_args + current_args=$(jq -r '.current[]?' "$KARGS_STATE_FILE" 2>/dev/null || echo "") + + # Combine current and pending arguments + local all_args=() + while IFS= read -r arg; do + if [[ -n "$arg" ]]; then + all_args+=("$arg") + fi + done < <(echo "$current_args") + + while IFS= read -r arg; do + if [[ -n "$arg" ]]; then + all_args+=("$arg") + fi + done < <(echo "$pending_args") + + # Update state file + local args_json + args_json=$(printf '%s\n' "${all_args[@]}" | jq -R . | jq -s .) + jq --argjson current "$args_json" '.current = $current | .pending = []' "$KARGS_STATE_FILE" > "$KARGS_STATE_FILE.tmp" && \ + mv "$KARGS_STATE_FILE.tmp" "$KARGS_STATE_FILE" + + log_success "Kernel arguments applied to deployment: $deployment_id" "apt-layer" + return 0 +} + +# Create bootloader entry for deployment +create_bootloader_entry() { + local deployment_id="$1" + local deployment_dir="$2" + local title="${3:-Ubuntu uBlue}" + + if [[ -z "$deployment_id" ]] || [[ -z "$deployment_dir" ]]; then + log_error "Deployment ID and directory required" "apt-layer" + return 1 + fi + + log_info "Creating bootloader entry for deployment: $deployment_id" "apt-layer" + + # Detect bootloader type + local bootloader_type + bootloader_type=$(detect_bootloader_type) + + case "$bootloader_type" in + "systemd-boot") + create_systemd_boot_entry "$deployment_id" "$deployment_dir" "$title" + ;; + "grub-uefi"|"grub-legacy") + create_grub_boot_entry "$deployment_id" "$deployment_dir" "$title" + ;; + "uefi") + create_uefi_boot_entry "$deployment_id" "$deployment_dir" "$title" + ;; + *) + log_warning "Unsupported bootloader type: $bootloader_type" "apt-layer" + return 1 + ;; + esac + + return 0 +} + +# Create systemd-boot entry +create_systemd_boot_entry() { + local deployment_id="$1" + local deployment_dir="$2" + local title="$3" + + log_info "Creating systemd-boot entry" "apt-layer" + + local entry_file="/boot/loader/entries/${deployment_id}.conf" + local kernel_path="$deployment_dir/vmlinuz" + local initrd_path="$deployment_dir/initrd.img" + + # Check if kernel and initrd exist + if [[ ! -f "$kernel_path" ]]; then + log_error "Kernel not found: $kernel_path" "apt-layer" + return 1 + fi + + if [[ ! -f "$initrd_path" ]]; then + log_error "Initrd not found: $initrd_path" "apt-layer" + return 1 + fi + + # Get kernel arguments + local kargs_file="$BOOTLOADER_ENTRIES_DIR/${deployment_id}.kargs" + local kargs="" + if [[ -f "$kargs_file" ]]; then + kargs=$(cat "$kargs_file" | grep -v '^#' | tr '\n' ' ') + fi + + # Create systemd-boot entry + cat > "$entry_file" << EOF +title $title ($deployment_id) +linux $kernel_path +initrd $initrd_path +options root=UUID=$(get_root_uuid) ro $kargs +EOF + + log_success "systemd-boot entry created: $entry_file" "apt-layer" + return 0 +} + +# Create GRUB boot entry +create_grub_boot_entry() { + local deployment_id="$1" + local deployment_dir="$2" + local title="$3" + + log_info "Creating GRUB boot entry" "apt-layer" + + # This would typically involve updating /etc/default/grub and running update-grub + # For now, we'll create a custom GRUB configuration snippet + local grub_config_dir="/etc/grub.d" + local grub_script="$grub_config_dir/10_${deployment_id}" + + if [[ ! -d "$grub_config_dir" ]]; then + log_error "GRUB configuration directory not found: $grub_config_dir" "apt-layer" + return 1 + fi + + # Get kernel arguments + local kargs_file="$BOOTLOADER_ENTRIES_DIR/${deployment_id}.kargs" + local kargs="" + if [[ -f "$kargs_file" ]]; then + kargs=$(cat "$kargs_file" | grep -v '^#' | tr '\n' ' ') + fi + + # Create GRUB script + cat > "$grub_script" << EOF +#!/bin/sh +exec tail -n +3 \$0 +menuentry '$title ($deployment_id)' { + linux $deployment_dir/vmlinuz root=UUID=$(get_root_uuid) ro $kargs + initrd $deployment_dir/initrd.img +} +EOF + + chmod +x "$grub_script" + + # Update GRUB configuration + if command -v update-grub &>/dev/null; then + if update-grub; then + log_success "GRUB configuration updated" "apt-layer" + else + log_warning "Failed to update GRUB configuration" "apt-layer" + fi + fi + + log_success "GRUB boot entry created: $grub_script" "apt-layer" + return 0 +} + +# Create UEFI boot entry +create_uefi_boot_entry() { + local deployment_id="$1" + local deployment_dir="$2" + local title="$3" + + log_info "Creating UEFI boot entry" "apt-layer" + + if ! command -v efibootmgr &>/dev/null; then + log_error "efibootmgr not available" "apt-layer" + return 1 + fi + + # Find EFI partition + local efi_partition + efi_partition=$(find_efi_partition) + if [[ -z "$efi_partition" ]]; then + log_error "EFI partition not found" "apt-layer" + return 1 + fi + + # Get kernel arguments + local kargs_file="$BOOTLOADER_ENTRIES_DIR/${deployment_id}.kargs" + local kargs="" + if [[ -f "$kargs_file" ]]; then + kargs=$(cat "$kargs_file" | grep -v '^#' | tr '\n' ' ') + fi + + # Create UEFI boot entry + local kernel_path="$deployment_dir/vmlinuz" + local boot_args="root=UUID=$(get_root_uuid) ro $kargs" + + if efibootmgr --create --disk "$efi_partition" --part 1 --label "$title ($deployment_id)" --loader "$kernel_path" --unicode "$boot_args"; then + log_success "UEFI boot entry created" "apt-layer" + return 0 + else + log_error "Failed to create UEFI boot entry" "apt-layer" + return 1 + fi +} + +# Get root device UUID +get_root_uuid() { + local root_device + root_device=$(findmnt -n -o SOURCE /) + + if [[ -n "$root_device" ]]; then + blkid -s UUID -o value "$root_device" 2>/dev/null || echo "unknown" + else + echo "unknown" + fi +} + +# Find EFI partition +find_efi_partition() { + # Look for EFI partition in /proc/partitions + local efi_partition + efi_partition=$(lsblk -n -o NAME,MOUNTPOINT,FSTYPE | grep -E '/boot/efi|/efi' | awk '{print $1}' | head -1) + + if [[ -n "$efi_partition" ]]; then + echo "/dev/$efi_partition" + else + # Fallback: look for EFI partition by filesystem type + lsblk -n -o NAME,FSTYPE | grep vfat | awk '{print "/dev/" $1}' | head -1 + fi +} + +# Set default boot entry +set_default_boot_entry() { + local deployment_id="$1" + + if [[ -z "$deployment_id" ]]; then + log_error "Deployment ID required" "apt-layer" + return 1 + fi + + log_info "Setting default boot entry: $deployment_id" "apt-layer" + + # Detect bootloader type + local bootloader_type + bootloader_type=$(detect_bootloader_type) + + case "$bootloader_type" in + "systemd-boot") + set_systemd_boot_default "$deployment_id" + ;; + "grub-uefi"|"grub-legacy") + set_grub_default "$deployment_id" + ;; + "uefi") + set_uefi_default "$deployment_id" + ;; + *) + log_warning "Unsupported bootloader type: $bootloader_type" "apt-layer" + return 1 + ;; + esac + + return 0 +} + +# Set systemd-boot default +set_systemd_boot_default() { + local deployment_id="$1" + + local loader_conf="/boot/loader/loader.conf" + local entry_file="/boot/loader/entries/${deployment_id}.conf" + + if [[ ! -f "$entry_file" ]]; then + log_error "Boot entry not found: $entry_file" "apt-layer" + return 1 + fi + + # Update loader.conf + if [[ -f "$loader_conf" ]]; then + # Backup original + cp "$loader_conf" "$loader_conf.backup" + + # Update default entry + sed -i "s/^default.*/default $deployment_id/" "$loader_conf" 2>/dev/null || \ + echo "default $deployment_id" >> "$loader_conf" + else + # Create loader.conf + cat > "$loader_conf" << EOF +default $deployment_id +timeout 5 +editor no +EOF + fi + + log_success "systemd-boot default set to: $deployment_id" "apt-layer" + return 0 +} + +# Set GRUB default +set_grub_default() { + local deployment_id="$1" + + local grub_default="/etc/default/grub" + + if [[ -f "$grub_default" ]]; then + # Backup original + cp "$grub_default" "$grub_default.backup" + + # Update default entry + sed -i "s/^GRUB_DEFAULT.*/GRUB_DEFAULT=\"$deployment_id\"/" "$grub_default" 2>/dev/null || \ + echo "GRUB_DEFAULT=\"$deployment_id\"" >> "$grub_default" + + # Update GRUB configuration + if command -v update-grub &>/dev/null; then + if update-grub; then + log_success "GRUB default set to: $deployment_id" "apt-layer" + return 0 + else + log_error "Failed to update GRUB configuration" "apt-layer" + return 1 + fi + fi + else + log_error "GRUB default configuration not found: $grub_default" "apt-layer" + return 1 + fi +} + +# Set UEFI default +set_uefi_default() { + local deployment_id="$1" + + if ! command -v efibootmgr &>/dev/null; then + log_error "efibootmgr not available" "apt-layer" + return 1 + fi + + # Find boot entry + local boot_entry + boot_entry=$(efibootmgr | grep "$deployment_id" | head -1 | sed 's/Boot\([0-9a-fA-F]*\).*/\1/') + + if [[ -n "$boot_entry" ]]; then + if efibootmgr --bootnext "$boot_entry"; then + log_success "UEFI default set to: $deployment_id" "apt-layer" + return 0 + else + log_error "Failed to set UEFI default" "apt-layer" + return 1 + fi + else + log_error "UEFI boot entry not found: $deployment_id" "apt-layer" + return 1 + fi +} + +# List boot entries +list_boot_entries() { + log_info "Listing boot entries" "apt-layer" + + # Detect bootloader type + local bootloader_type + bootloader_type=$(detect_bootloader_type) + + echo "=== Boot Entries ($bootloader_type) ===" + + case "$bootloader_type" in + "systemd-boot") + list_systemd_boot_entries + ;; + "grub-uefi"|"grub-legacy") + list_grub_entries + ;; + "uefi") + list_uefi_entries + ;; + *) + log_warning "Unsupported bootloader type: $bootloader_type" "apt-layer" + ;; + esac + + echo "" +} + +# List systemd-boot entries +list_systemd_boot_entries() { + local entries_dir="/boot/loader/entries" + + if [[ -d "$entries_dir" ]]; then + for entry in "$entries_dir"/*.conf; do + if [[ -f "$entry" ]]; then + local title + title=$(grep "^title" "$entry" | cut -d' ' -f2- | head -1) + local deployment_id + deployment_id=$(basename "$entry" .conf) + echo " $deployment_id: $title" + fi + done + else + log_info "No systemd-boot entries found" "apt-layer" + fi +} + +# List GRUB entries +list_grub_entries() { + local grub_cfg="/boot/grub/grub.cfg" + + if [[ -f "$grub_cfg" ]]; then + grep -A1 "menuentry" "$grub_cfg" | grep -E "(menuentry|particle-os)" | while read -r line; do + if [[ "$line" =~ menuentry ]]; then + local title + title=$(echo "$line" | sed 's/.*menuentry '\''\([^'\'']*\)'\''.*/\1/') + echo " $title" + fi + done + else + log_info "No GRUB entries found" "apt-layer" + fi +} + +# List UEFI entries +list_uefi_entries() { + if command -v efibootmgr &>/dev/null; then + efibootmgr | grep -E "Boot[0-9a-fA-F]*" | while read -r line; do + local boot_id + boot_id=$(echo "$line" | sed 's/Boot\([0-9a-fA-F]*\).*/\1/') + local title + title=$(echo "$line" | sed 's/.*\* \(.*\)/\1/') + echo " $boot_id: $title" + done + else + log_info "efibootmgr not available" "apt-layer" + fi +} + +# Remove boot entry +remove_boot_entry() { + local deployment_id="$1" + + if [[ -z "$deployment_id" ]]; then + log_error "Deployment ID required" "apt-layer" + return 1 + fi + + log_info "Removing boot entry: $deployment_id" "apt-layer" + + # Detect bootloader type + local bootloader_type + bootloader_type=$(detect_bootloader_type) + + case "$bootloader_type" in + "systemd-boot") + remove_systemd_boot_entry "$deployment_id" + ;; + "grub-uefi"|"grub-legacy") + remove_grub_entry "$deployment_id" + ;; + "uefi") + remove_uefi_entry "$deployment_id" + ;; + *) + log_warning "Unsupported bootloader type: $bootloader_type" "apt-layer" + return 1 + ;; + esac + + return 0 +} + +# Remove systemd-boot entry +remove_systemd_boot_entry() { + local deployment_id="$1" + + local entry_file="/boot/loader/entries/${deployment_id}.conf" + + if [[ -f "$entry_file" ]]; then + if rm "$entry_file"; then + log_success "systemd-boot entry removed: $deployment_id" "apt-layer" + return 0 + else + log_error "Failed to remove systemd-boot entry" "apt-layer" + return 1 + fi + else + log_warning "systemd-boot entry not found: $deployment_id" "apt-layer" + return 0 + fi +} + +# Remove GRUB entry +remove_grub_entry() { + local deployment_id="$1" + + local grub_script="/etc/grub.d/10_${deployment_id}" + + if [[ -f "$grub_script" ]]; then + if rm "$grub_script"; then + log_success "GRUB entry removed: $deployment_id" "apt-layer" + + # Update GRUB configuration + if command -v update-grub &>/dev/null; then + update-grub + fi + + return 0 + else + log_error "Failed to remove GRUB entry" "apt-layer" + return 1 + fi + else + log_warning "GRUB entry not found: $deployment_id" "apt-layer" + return 0 + fi +} + +# Remove UEFI entry +remove_uefi_entry() { + local deployment_id="$1" + + if ! command -v efibootmgr &>/dev/null; then + log_error "efibootmgr not available" "apt-layer" + return 1 + fi + + # Find boot entry + local boot_entry + boot_entry=$(efibootmgr | grep "$deployment_id" | head -1 | sed 's/Boot\([0-9a-fA-F]*\).*/\1/') + + if [[ -n "$boot_entry" ]]; then + if efibootmgr --bootnum "$boot_entry" --delete-bootnum; then + log_success "UEFI entry removed: $deployment_id" "apt-layer" + return 0 + else + log_error "Failed to remove UEFI entry" "apt-layer" + return 1 + fi + else + log_warning "UEFI entry not found: $deployment_id" "apt-layer" + return 0 + fi +} + +# Get bootloader status +get_bootloader_status() { + log_info "Getting bootloader status" "apt-layer" + + echo "=== Bootloader Status ===" + + # Detect bootloader type + local bootloader_type + bootloader_type=$(detect_bootloader_type) + echo "Bootloader Type: $bootloader_type" + + # Check secure boot status + if is_secure_boot_enabled; then + echo "Secure Boot: Enabled" + else + echo "Secure Boot: Disabled" + fi + + # Show current kernel arguments + echo "" + echo "Current Kernel Arguments:" + local current_args + current_args=$(get_current_kernel_args) + if [[ -n "$current_args" ]]; then + echo "$current_args" | tr ' ' '\n' | while read -r arg; do + if [[ -n "$arg" ]]; then + echo " $arg" + fi + done + else + echo " None" + fi + + # Show pending kernel arguments + echo "" + echo "Pending Kernel Arguments:" + local pending_args + pending_args=$(jq -r '.pending[]?' "$KARGS_STATE_FILE" 2>/dev/null || echo "") + if [[ -n "$pending_args" ]]; then + echo "$pending_args" | while read -r arg; do + if [[ -n "$arg" ]]; then + echo " $arg (pending)" + fi + done + else + echo " None" + fi + + echo "" +} + +# ============================================================================= +# INTEGRATION FUNCTIONS +# ============================================================================= + +# Initialize bootloader system on script startup +init_bootloader_on_startup() { + # Only initialize if not already done + if [[ ! -d "$BOOTLOADER_STATE_DIR" ]]; then + init_bootloader_system + fi +} + +# Cleanup bootloader on script exit +cleanup_bootloader_on_exit() { + # Clean up temporary files + rm -f "$KARGS_STATE_FILE.tmp" 2>/dev/null || true +} + +# Register cleanup function +trap cleanup_bootloader_on_exit EXIT + +# --- END OF SCRIPTLET: 07-bootloader.sh --- + +# ============================================================================ +# System Initialization and Path Management +# ============================================================================ + +# System Initialization and Path Management for apt-layer +# This scriptlet handles system initialization, path management, and directory creation + +# Load path configuration +load_path_config() { + local config_file="/usr/local/etc/apt-layer/paths.json" + + if [[ ! -f "$config_file" ]]; then + log_error "Path configuration file not found: $config_file" "apt-layer" + return 1 + fi + + # Load configuration using jq + if ! command -v jq >/dev/null 2>&1; then + log_error "jq is required for path configuration loading" "apt-layer" + return 1 + fi + + # Export main directory paths + export APT_LAYER_WORKSPACE=$(jq -r '.apt_layer_paths.main_directories.workspace.path' "$config_file") + export APT_LAYER_LOG_DIR=$(jq -r '.apt_layer_paths.main_directories.logs.path' "$config_file") + export APT_LAYER_CACHE_DIR=$(jq -r '.apt_layer_paths.main_directories.cache.path' "$config_file") + + # Export workspace subdirectory paths + export BUILD_DIR=$(jq -r '.apt_layer_paths.workspace_subdirectories.build.path' "$config_file") + export LIVE_OVERLAY_DIR=$(jq -r '.apt_layer_paths.workspace_subdirectories.live_overlay.path' "$config_file") + export COMPOSEFS_DIR=$(jq -r '.apt_layer_paths.workspace_subdirectories.composefs.path' "$config_file") + export OSTREE_COMMITS_DIR=$(jq -r '.apt_layer_paths.workspace_subdirectories.ostree_commits.path' "$config_file") + export DEPLOYMENTS_DIR=$(jq -r '.apt_layer_paths.workspace_subdirectories.deployments.path' "$config_file") + export HISTORY_DIR=$(jq -r '.apt_layer_paths.workspace_subdirectories.history.path' "$config_file") + export BOOTLOADER_STATE_DIR=$(jq -r '.apt_layer_paths.workspace_subdirectories.bootloader.path' "$config_file") + export TRANSACTION_STATE=$(jq -r '.apt_layer_paths.workspace_subdirectories.transaction_state.path' "$config_file") + + # Export file paths + export DEPLOYMENT_DB=$(jq -r '.apt_layer_paths.files.deployment_db.path' "$config_file") + export CURRENT_DEPLOYMENT_FILE=$(jq -r '.apt_layer_paths.files.current_deployment.path' "$config_file") + export PENDING_DEPLOYMENT_FILE=$(jq -r '.apt_layer_paths.files.pending_deployment.path' "$config_file") + export TRANSACTION_LOG=$(jq -r '.apt_layer_paths.files.transaction_log.path' "$config_file") + + log_debug "Path configuration loaded from: $config_file" "apt-layer" + return 0 +} + +# Initialize apt-layer system directories +initialize_apt_layer_system() { + log_info "Initializing apt-layer system directories..." "apt-layer" + + # Load path configuration + if ! load_path_config; then + log_error "Failed to load path configuration" "apt-layer" + return 1 + fi + + # Create main directories + local main_dirs=("$APT_LAYER_WORKSPACE" "$APT_LAYER_LOG_DIR" "$APT_LAYER_CACHE_DIR") + for dir in "${main_dirs[@]}"; do + if [[ ! -d "$dir" ]]; then + mkdir -p "$dir" + chmod 755 "$dir" + chown root:root "$dir" + log_debug "Created directory: $dir" "apt-layer" + fi + done + + # Create workspace subdirectories + local subdirs=( + "$BUILD_DIR" + "$LIVE_OVERLAY_DIR" + "$COMPOSEFS_DIR" + "$OSTREE_COMMITS_DIR" + "$DEPLOYMENTS_DIR" + "$HISTORY_DIR" + "$BOOTLOADER_STATE_DIR" + "$TRANSACTION_STATE" + ) + + for dir in "${subdirs[@]}"; do + if [[ ! -d "$dir" ]]; then + mkdir -p "$dir" + chmod 755 "$dir" + chown root:root "$dir" + log_debug "Created subdirectory: $dir" "apt-layer" + fi + done + + # Create live overlay subdirectories + local overlay_dirs=( + "$LIVE_OVERLAY_DIR/upper" + "$LIVE_OVERLAY_DIR/work" + "$LIVE_OVERLAY_DIR/mount" + ) + + for dir in "${overlay_dirs[@]}"; do + if [[ ! -d "$dir" ]]; then + mkdir -p "$dir" + chmod 700 "$dir" + chown root:root "$dir" + log_debug "Created overlay directory: $dir" "apt-layer" + fi + done + + # Initialize deployment database if it doesn't exist + if [[ ! -f "$DEPLOYMENT_DB" ]]; then + echo '{"deployments": {}, "current": null, "history": []}' > "$DEPLOYMENT_DB" + chmod 644 "$DEPLOYMENT_DB" + chown root:root "$DEPLOYMENT_DB" + log_debug "Initialized deployment database: $DEPLOYMENT_DB" "apt-layer" + fi + + log_success "apt-layer system directories initialized" "apt-layer" + return 0 +} + +# Reinitialize apt-layer system (force recreation) +reinitialize_apt_layer_system() { + log_info "Reinitializing apt-layer system (force recreation)..." "apt-layer" + + # Load path configuration + if ! load_path_config; then + log_error "Failed to load path configuration" "apt-layer" + return 1 + fi + + # Remove existing directories + local dirs_to_remove=( + "$APT_LAYER_WORKSPACE" + "$APT_LAYER_LOG_DIR" + "$APT_LAYER_CACHE_DIR" + ) + + for dir in "${dirs_to_remove[@]}"; do + if [[ -d "$dir" ]]; then + rm -rf "$dir" + log_debug "Removed directory: $dir" "apt-layer" + fi + done + + # Reinitialize + if initialize_apt_layer_system; then + log_success "apt-layer system reinitialized successfully" "apt-layer" + return 0 + else + log_error "Failed to reinitialize apt-layer system" "apt-layer" + return 1 + fi +} + +# Remove apt-layer system (cleanup) +remove_apt_layer_system() { + log_info "Removing apt-layer system (cleanup)..." "apt-layer" + + # Load path configuration + if ! load_path_config; then + log_error "Failed to load path configuration" "apt-layer" + return 1 + fi + + # Stop any running live overlay + if is_live_overlay_active; then + log_warning "Live overlay is active, stopping it first..." "apt-layer" + stop_live_overlay + fi + + # Remove all apt-layer directories + local dirs_to_remove=( + "$APT_LAYER_WORKSPACE" + "$APT_LAYER_LOG_DIR" + "$APT_LAYER_CACHE_DIR" + ) + + for dir in "${dirs_to_remove[@]}"; do + if [[ -d "$dir" ]]; then + rm -rf "$dir" + log_debug "Removed directory: $dir" "apt-layer" + fi + done + + log_success "apt-layer system removed successfully" "apt-layer" + return 0 +} + +# Show apt-layer system status +show_apt_layer_system_status() { + log_info "apt-layer System Status:" "apt-layer" + + # Load path configuration + if ! load_path_config; then + log_error "Failed to load path configuration" "apt-layer" + return 1 + fi + + echo "=== Main Directories ===" + local main_dirs=( + ["Workspace"]="$APT_LAYER_WORKSPACE" + ["Logs"]="$APT_LAYER_LOG_DIR" + ["Cache"]="$APT_LAYER_CACHE_DIR" + ) + + for name in "${!main_dirs[@]}"; do + local dir="${main_dirs[$name]}" + if [[ -d "$dir" ]]; then + local size=$(du -sh "$dir" 2>/dev/null | cut -f1) + local perms=$(stat -c "%a" "$dir" 2>/dev/null) + echo "✅ $name: $dir ($size, perms: $perms)" + else + echo "❌ $name: $dir (not found)" + fi + done + + echo "" + echo "=== Workspace Subdirectories ===" + local subdirs=( + ["Build"]="$BUILD_DIR" + ["Live Overlay"]="$LIVE_OVERLAY_DIR" + ["ComposeFS"]="$COMPOSEFS_DIR" + ["OSTree Commits"]="$OSTREE_COMMITS_DIR" + ["Deployments"]="$DEPLOYMENTS_DIR" + ["History"]="$HISTORY_DIR" + ["Bootloader"]="$BOOTLOADER_STATE_DIR" + ["Transaction State"]="$TRANSACTION_STATE" + ) + + for name in "${!subdirs[@]}"; do + local dir="${subdirs[$name]}" + if [[ -d "$dir" ]]; then + local count=$(find "$dir" -maxdepth 1 -type f 2>/dev/null | wc -l) + echo "✅ $name: $dir ($count files)" + else + echo "❌ $name: $dir (not found)" + fi + done + + echo "" + echo "=== System Files ===" + local files=( + ["Deployment DB"]="$DEPLOYMENT_DB" + ["Current Deployment"]="$CURRENT_DEPLOYMENT_FILE" + ["Pending Deployment"]="$PENDING_DEPLOYMENT_FILE" + ["Transaction Log"]="$TRANSACTION_LOG" + ) + + for name in "${!files[@]}"; do + local file="${files[$name]}" + if [[ -f "$file" ]]; then + local size=$(stat -c "%s" "$file" 2>/dev/null) + echo "✅ $name: $file ($size bytes)" + else + echo "❌ $name: $file (not found)" + fi + done + + echo "" + echo "=== Live Overlay Status ===" + if is_live_overlay_active; then + echo "🟡 Live overlay is ACTIVE" + echo " Mount point: $LIVE_OVERLAY_DIR/mount" + echo " Upper dir: $LIVE_OVERLAY_DIR/upper" + echo " Work dir: $LIVE_OVERLAY_DIR/work" + else + echo "🟢 Live overlay is INACTIVE" + fi + + return 0 +} + +# Validate path configuration +validate_path_config() { + log_debug "Validating path configuration..." "apt-layer" + + local config_file="/usr/local/etc/apt-layer/paths.json" + + if [[ ! -f "$config_file" ]]; then + log_error "Path configuration file not found: $config_file" "apt-layer" + return 1 + fi + + # Validate JSON syntax + if ! jq empty "$config_file" 2>/dev/null; then + log_error "Invalid JSON in path configuration file" "apt-layer" + return 1 + fi + + # Validate required paths exist in config + local required_paths=( + ".apt_layer_paths.main_directories.workspace.path" + ".apt_layer_paths.main_directories.logs.path" + ".apt_layer_paths.main_directories.cache.path" + ) + + for path in "${required_paths[@]}"; do + if ! jq -e "$path" "$config_file" >/dev/null 2>&1; then + log_error "Required path not found in config: $path" "apt-layer" + return 1 + fi + done + + log_debug "Path configuration validation passed" "apt-layer" + return 0 +} + +# Handle system initialization commands +handle_system_init_commands() { + case "$1" in + "--init") + if initialize_apt_layer_system; then + log_success "apt-layer system initialized successfully" "apt-layer" + return 0 + else + log_error "Failed to initialize apt-layer system" "apt-layer" + return 1 + fi + ;; + "--reinit") + if reinitialize_apt_layer_system; then + log_success "apt-layer system reinitialized successfully" "apt-layer" + return 0 + else + log_error "Failed to reinitialize apt-layer system" "apt-layer" + return 1 + fi + ;; + "--rm-init") + if remove_apt_layer_system; then + log_success "apt-layer system removed successfully" "apt-layer" + return 0 + else + log_error "Failed to remove apt-layer system" "apt-layer" + return 1 + fi + ;; + "--status") + show_apt_layer_system_status + return $? + ;; + *) + return 1 + ;; + esac +} + +# --- END OF SCRIPTLET: 08-system-init.sh --- + +# ============================================================================ +# Atomic Deployment System +# ============================================================================ +# Atomic deployment system for Ubuntu uBlue apt-layer Tool +# Implements commit-based state management and true system upgrades (not package upgrades) + +# Atomic deployment state management +DEPLOYMENT_DB="/var/lib/particle-os/deployments.json" +CURRENT_DEPLOYMENT_FILE="/var/lib/particle-os/current-deployment" +PENDING_DEPLOYMENT_FILE="/var/lib/particle-os/pending-deployment" +DEPLOYMENT_HISTORY_DIR="/var/lib/particle-os/history" + +# Initialize deployment database +init_deployment_db() { + log_info "Initializing atomic deployment database..." "apt-layer" + + # Ensure directories exist with proper permissions + mkdir -p "$DEPLOYMENT_HISTORY_DIR" 2>/dev/null || { + log_error "Failed to create deployment history directory: $DEPLOYMENT_HISTORY_DIR" "apt-layer" + return 1 + } + + # Create deployment database if it doesn't exist + if [[ ! -f "$DEPLOYMENT_DB" ]]; then + cat > "$DEPLOYMENT_DB" << 'EOF' +{ + "deployments": {}, + "current_deployment": null, + "pending_deployment": null, + "deployment_counter": 0, + "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" +} +EOF + if [[ $? -eq 0 ]]; then + log_success "Deployment database initialized" "apt-layer" + else + log_error "Failed to create deployment database: $DEPLOYMENT_DB" "apt-layer" + return 1 + fi + fi + + # Ensure deployment files exist with proper error handling + touch "$CURRENT_DEPLOYMENT_FILE" 2>/dev/null || { + log_warning "Failed to create current deployment file, attempting with sudo..." "apt-layer" + sudo touch "$CURRENT_DEPLOYMENT_FILE" 2>/dev/null || { + log_error "Failed to create current deployment file: $CURRENT_DEPLOYMENT_FILE" "apt-layer" + return 1 + } + } + + touch "$PENDING_DEPLOYMENT_FILE" 2>/dev/null || { + log_warning "Failed to create pending deployment file, attempting with sudo..." "apt-layer" + sudo touch "$PENDING_DEPLOYMENT_FILE" 2>/dev/null || { + log_error "Failed to create pending deployment file: $PENDING_DEPLOYMENT_FILE" "apt-layer" + return 1 + } + } + + log_success "Deployment database initialization completed" "apt-layer" +} + +# Create a new deployment commit +create_deployment_commit() { + local base_image="$1" + local layers=("${@:2}") + local commit_message="${COMMIT_MESSAGE:-System update}" + + local commit_id="commit-$(date +%Y%m%d-%H%M%S)-$$" + local commit_data + + log_info "Creating deployment commit: $commit_id" "apt-layer" + + # Create commit metadata with proper variable expansion + local layers_json="[" + for i in "${!layers[@]}"; do + if [[ $i -gt 0 ]]; then + layers_json+="," + fi + layers_json+="\"${layers[$i]}\"" + done + layers_json+="]" + + commit_data=$(cat << EOF +{ + "commit_id": "$commit_id", + "base_image": "$base_image", + "layers": $layers_json, + "commit_message": "$commit_message", + "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "parent_commit": "$(get_current_deployment)", + "composefs_image": "${commit_id}.composefs" +} +EOF +) + + # Add to deployment database + jq --arg commit_id "$commit_id" \ + --arg base_image "$base_image" \ + --arg layers_json "$layers_json" \ + --arg commit_message "$commit_message" \ + --arg created "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --arg parent_commit "$(get_current_deployment)" \ + --arg composefs_image "${commit_id}.composefs" \ + '.deployments[$commit_id] = { + "commit_id": $commit_id, + "base_image": $base_image, + "layers": ($layers_json | fromjson), + "commit_message": $commit_message, + "created": $created, + "parent_commit": $parent_commit, + "composefs_image": $composefs_image + } | .deployment_counter += 1' \ + "$DEPLOYMENT_DB" > "${DEPLOYMENT_DB}.tmp" && mv "${DEPLOYMENT_DB}.tmp" "$DEPLOYMENT_DB" + + # Create deployment history file + echo "$commit_data" > "$DEPLOYMENT_HISTORY_DIR/$commit_id.json" + + log_success "Deployment commit created: $commit_id" "apt-layer" + echo "$commit_id" +} + +# Get current deployment +get_current_deployment() { + if [[ -f "$CURRENT_DEPLOYMENT_FILE" ]]; then + cat "$CURRENT_DEPLOYMENT_FILE" 2>/dev/null || echo "" + else + echo "" + fi +} + +# Get pending deployment +get_pending_deployment() { + if [[ -f "$PENDING_DEPLOYMENT_FILE" ]]; then + cat "$PENDING_DEPLOYMENT_FILE" 2>/dev/null || echo "" + else + echo "" + fi +} + +# Set current deployment +set_current_deployment() { + local commit_id="$1" + echo "$commit_id" > "$CURRENT_DEPLOYMENT_FILE" + + # Update deployment database + jq --arg commit_id "$commit_id" '.current_deployment = $commit_id' \ + "$DEPLOYMENT_DB" > "${DEPLOYMENT_DB}.tmp" && mv "${DEPLOYMENT_DB}.tmp" "$DEPLOYMENT_DB" + + log_info "Current deployment set to: $commit_id" "apt-layer" +} + +# Set pending deployment +set_pending_deployment() { + local commit_id="$1" + echo "$commit_id" > "$PENDING_DEPLOYMENT_FILE" + + # Update deployment database + jq --arg commit_id "$commit_id" '.pending_deployment = $commit_id' \ + "$DEPLOYMENT_DB" > "${DEPLOYMENT_DB}.tmp" && mv "${DEPLOYMENT_DB}.tmp" "$DEPLOYMENT_DB" + + log_info "Pending deployment set to: $commit_id" "apt-layer" +} + +# Clear pending deployment +clear_pending_deployment() { + echo "" > "$PENDING_DEPLOYMENT_FILE" + + # Update deployment database + jq '.pending_deployment = null' \ + "$DEPLOYMENT_DB" > "${DEPLOYMENT_DB}.tmp" && mv "${DEPLOYMENT_DB}.tmp" "$DEPLOYMENT_DB" + + log_info "Pending deployment cleared" "apt-layer" +} + +# Atomic deployment function +atomic_deploy() { + local commit_id="$1" + local deployment_dir="/var/lib/particle-os/deployments/${commit_id}" + local boot_dir="/boot/loader/entries" + + log_info "Performing atomic deployment: $commit_id" "apt-layer" + + # Validate commit exists + if ! jq -e ".deployments[\"$commit_id\"]" "$DEPLOYMENT_DB" >/dev/null 2>&1; then + log_error "Commit not found: $commit_id" "apt-layer" + return 1 + fi + + # Get commit data + local commit_data + commit_data=$(jq -r ".deployments[\"$commit_id\"]" "$DEPLOYMENT_DB") + local composefs_image + composefs_image=$(echo "$commit_data" | jq -r '.composefs_image') + + # Create deployment directory + mkdir -p "$deployment_dir" + + # Mount the ComposeFS image + if ! composefs_mount "$composefs_image" "$deployment_dir"; then + log_error "Failed to mount ComposeFS image for deployment" "apt-layer" + return 1 + fi + + # Apply kernel arguments to deployment + apply_kernel_args_to_deployment "$commit_id" + + # Create bootloader entry + create_bootloader_entry "$commit_id" "$deployment_dir" + + # Set as pending deployment (will activate on next boot) + set_pending_deployment "$commit_id" + + log_success "Atomic deployment prepared: $commit_id" "apt-layer" + log_info "Reboot to activate deployment" "apt-layer" + return 0 +} + +# True system upgrade (not package upgrade) +system_upgrade() { + local new_base_image="${1:-}" + local current_layers=() + + log_info "Performing true system upgrade..." "apt-layer" + + # Get current deployment + local current_commit + current_commit=$(get_current_deployment) + + if [[ -n "$current_commit" ]]; then + # Get current layers from deployment + current_layers=($(jq -r ".deployments[\"$current_commit\"].layers[]" "$DEPLOYMENT_DB" 2>/dev/null || true)) + log_info "Current layers: ${current_layers[*]}" "apt-layer" + fi + + # If no new base specified, try to find one + if [[ -z "$new_base_image" ]]; then + new_base_image=$(find_newer_base_image) + if [[ -z "$new_base_image" ]]; then + log_info "No newer base image found" "apt-layer" + return 0 + fi + fi + + log_info "Upgrading to base image: $new_base_image" "apt-layer" + + # Rebase existing layers on new base + local rebased_layers=() + for layer in "${current_layers[@]}"; do + local new_layer="${layer}-rebased-$(date +%Y%m%d)" + log_info "Rebasing layer: $layer -> $new_layer" "apt-layer" + + if "$0" --rebase "$layer" "$new_base_image" "$new_layer"; then + rebased_layers+=("$new_layer") + else + log_error "Failed to rebase layer: $layer" "apt-layer" + return 1 + fi + done + + # Create new deployment commit + local commit_id + commit_id=$(create_deployment_commit "$new_base_image" "${rebased_layers[@]}") + + # Perform atomic deployment + if atomic_deploy "$commit_id"; then + log_success "System upgrade completed successfully" "apt-layer" + return 0 + else + log_error "System upgrade failed" "apt-layer" + return 1 + fi +} + +# Find newer base image +find_newer_base_image() { + local current_base + current_base=$(jq -r ".deployments[\"$(get_current_deployment)\"].base_image" "$DEPLOYMENT_DB" 2>/dev/null || echo "") + + if [[ -z "$current_base" ]]; then + log_warning "No current base image found" "apt-layer" + return 1 + fi + + # List available base images and find newer ones + local available_bases + available_bases=($(composefs_list_images | grep "^particle-os/base/" | sort -V)) + + for base in "${available_bases[@]}"; do + if [[ "$base" > "$current_base" ]]; then + echo "$base" + return 0 + fi + done + + return 1 +} + +# Create bootloader entry +create_bootloader_entry() { + local commit_id="$1" + local deployment_dir="$2" + + log_info "Creating bootloader entry for: $commit_id" "apt-layer" + + # Initialize bootloader system + init_bootloader_on_startup + + # Create bootloader entry using the comprehensive bootloader system + if create_bootloader_entry "$commit_id" "$deployment_dir" "Ubuntu uBlue ($commit_id)"; then + log_success "Bootloader entry created for: $commit_id" "apt-layer" + return 0 + else + log_error "Failed to create bootloader entry for: $commit_id" "apt-layer" + return 1 + fi +} + +# Show atomic deployment status +atomic_status() { + local current_deployment + current_deployment=$(get_current_deployment) + local pending_deployment + pending_deployment=$(get_pending_deployment) + + echo "=== Atomic Deployment Status ===" + echo "Current Deployment: ${current_deployment:-none}" + echo "Pending Deployment: ${pending_deployment:-none}" + + if [[ -n "$current_deployment" ]]; then + local commit_data + commit_data=$(jq -r ".deployments[\"$current_deployment\"]" "$DEPLOYMENT_DB" 2>/dev/null || echo "{}") + + if [[ "$commit_data" != "{}" ]]; then + echo "Deployment Type: $(echo "$commit_data" | jq -r '.commit_message')" + echo "Base Image: $(echo "$commit_data" | jq -r '.base_image')" + echo "Created: $(echo "$commit_data" | jq -r '.created')" + echo "Layers: $(echo "$commit_data" | jq -r '.layers | join(", ")')" + fi + fi + + if [[ -n "$pending_deployment" ]]; then + echo "�� Pending deployment will activate on next boot" + fi +} + +# List all deployments +list_deployments() { + echo "=== Deployment History ===" + + local deployments + deployments=($(jq -r '.deployments | keys[]' "$DEPLOYMENT_DB" 2>/dev/null | sort -r)) + + for commit_id in "${deployments[@]}"; do + local commit_data + commit_data=$(jq -r ".deployments[\"$commit_id\"]" "$DEPLOYMENT_DB") + + local status="" + if [[ "$commit_id" == "$(get_current_deployment)" ]]; then + status=" [CURRENT]" + elif [[ "$commit_id" == "$(get_pending_deployment)" ]]; then + status=" [PENDING]" + fi + + echo "$commit_id$status" + echo " Message: $(echo "$commit_data" | jq -r '.commit_message')" + echo " Created: $(echo "$commit_data" | jq -r '.created')" + echo " Base: $(echo "$commit_data" | jq -r '.base_image')" + echo "" + done +} + +# Rollback to specific commit +commit_rollback() { + local target_commit="$1" + + log_info "Rolling back to commit: $target_commit" "apt-layer" + + # Validate target commit exists + if ! jq -e ".deployments[\"$target_commit\"]" "$DEPLOYMENT_DB" >/dev/null 2>&1; then + log_error "Target commit not found: $target_commit" "apt-layer" + return 1 + fi + + # Perform atomic deployment to target commit + if atomic_deploy "$target_commit"; then + log_success "Rollback prepared to: $target_commit" "apt-layer" + log_info "Reboot to activate rollback" "apt-layer" + return 0 + else + log_error "Rollback failed" "apt-layer" + return 1 + fi +} + +# --- END OF SCRIPTLET: 09-atomic-deployment.sh --- + +# ============================================================================ +# rpm-ostree Compatibility Layer +# ============================================================================ +# rpm-ostree compatibility layer for Ubuntu uBlue apt-layer Tool +# Provides 1:1 command compatibility with rpm-ostree + +# rpm-ostree install compatibility +rpm_ostree_install() { + local packages=("$@") + + log_info "rpm-ostree install compatibility: ${packages[*]}" "apt-layer" + + # Use live overlay for package installation + if ! live_install "${packages[@]}"; then + log_error "rpm-ostree install failed" "apt-layer" + return 1 + fi + + log_success "rpm-ostree install completed successfully" "apt-layer" + return 0 +} + +# rpm-ostree upgrade compatibility +rpm_ostree_upgrade() { + log_info "rpm-ostree upgrade compatibility" "apt-layer" + + # Use true system upgrade (not package upgrade) + if ! system_upgrade; then + log_error "rpm-ostree upgrade failed" "apt-layer" + return 1 + fi + + log_success "rpm-ostree upgrade completed successfully" "apt-layer" + return 0 +} + +# rpm-ostree rebase compatibility +rpm_ostree_rebase() { + local new_base="$1" + + log_info "rpm-ostree rebase compatibility: $new_base" "apt-layer" + + # Use intelligent rebase with conflict resolution + if ! intelligent_rebase "$new_base"; then + log_error "rpm-ostree rebase failed" "apt-layer" + return 1 + fi + + log_success "rpm-ostree rebase completed successfully" "apt-layer" + return 0 +} + +# rpm-ostree rollback compatibility +rpm_ostree_rollback() { + local target_commit="${1:-}" + + log_info "rpm-ostree rollback compatibility: ${target_commit:-latest}" "apt-layer" + + if [[ -z "$target_commit" ]]; then + # Rollback to previous deployment + target_commit=$(get_previous_deployment) + if [[ -z "$target_commit" ]]; then + log_error "No previous deployment found for rollback" "apt-layer" + return 1 + fi + fi + + # Use commit-based rollback + if ! commit_rollback "$target_commit"; then + log_error "rpm-ostree rollback failed" "apt-layer" + return 1 + fi + + log_success "rpm-ostree rollback completed successfully" "apt-layer" + return 0 +} + +# rpm-ostree status compatibility +rpm_ostree_status() { + log_info "rpm-ostree status compatibility" "apt-layer" + + # Show atomic deployment status + atomic_status + + # Show live overlay status + echo "" + echo "=== Live Overlay Status ===" + get_live_overlay_status + + # Show package diff if pending deployment + local pending_deployment + pending_deployment=$(get_pending_deployment) + if [[ -n "$pending_deployment" ]]; then + echo "" + echo "=== Pending Changes ===" + show_package_diff "$(get_current_deployment)" "$pending_deployment" + fi +} + +# rpm-ostree diff compatibility +rpm_ostree_diff() { + local from_commit="${1:-}" + local to_commit="${2:-}" + + log_info "rpm-ostree diff compatibility: $from_commit -> $to_commit" "apt-layer" + + # If no commits specified, compare current to pending + if [[ -z "$from_commit" ]]; then + from_commit=$(get_current_deployment) + fi + if [[ -z "$to_commit" ]]; then + to_commit=$(get_pending_deployment) + if [[ -z "$to_commit" ]]; then + log_error "No target commit specified and no pending deployment" "apt-layer" + return 1 + fi + fi + + # Show package-level diff + show_package_diff "$from_commit" "$to_commit" +} + +# rpm-ostree db list compatibility +rpm_ostree_db_list() { + log_info "rpm-ostree db list compatibility" "apt-layer" + + # List all deployments + list_deployments +} + +# rpm-ostree db diff compatibility +rpm_ostree_db_diff() { + local from_commit="${1:-}" + local to_commit="${2:-}" + + log_info "rpm-ostree db diff compatibility: $from_commit -> $to_commit" "apt-layer" + + # If no commits specified, compare current to pending + if [[ -z "$from_commit" ]]; then + from_commit=$(get_current_deployment) + fi + if [[ -z "$to_commit" ]]; then + to_commit=$(get_pending_deployment) + if [[ -z "$to_commit" ]]; then + log_error "No target commit specified and no pending deployment" "apt-layer" + return 1 + fi + fi + + # Show detailed package diff + show_detailed_package_diff "$from_commit" "$to_commit" +} + +# rpm-ostree cleanup compatibility +rpm_ostree_cleanup() { + local purge="${1:-}" + + log_info "rpm-ostree cleanup compatibility: purge=$purge" "apt-layer" + + # Clean up old deployments + cleanup_old_deployments + + # Clean up old ComposeFS images + cleanup_old_composefs_images + + if [[ "$purge" == "--purge" ]]; then + # Also clean up old bootloader entries + cleanup_old_bootloader_entries + fi + + log_success "rpm-ostree cleanup completed successfully" "apt-layer" +} + +# rpm-ostree cancel compatibility +rpm_ostree_cancel() { + log_info "rpm-ostree cancel compatibility" "apt-layer" + + # Clear pending deployment + clear_pending_deployment + + # Clean up live overlay + stop_live_overlay + + log_success "rpm-ostree cancel completed successfully" "apt-layer" +} + +# rpm-ostree initramfs compatibility +rpm_ostree_initramfs() { + local action="${1:-}" + + log_info "rpm-ostree initramfs compatibility: $action" "apt-layer" + + case "$action" in + --enable) + enable_initramfs_rebuild + ;; + --disable) + disable_initramfs_rebuild + ;; + --rebuild) + rebuild_initramfs + ;; + *) + log_error "Invalid initramfs action: $action" "apt-layer" + return 1 + ;; + esac +} + +# rpm-ostree kargs compatibility +rpm_ostree_kargs() { + local action="${1:-}" + shift + + log_info "rpm-ostree kargs compatibility: $action" "apt-layer" + + case "$action" in + --get) + get_kernel_args + ;; + --set) + set_kernel_args "$@" + ;; + --append) + append_kernel_args "$@" + ;; + --delete) + delete_kernel_args "$@" + ;; + --reset) + reset_kernel_args + ;; + *) + log_error "Invalid kargs action: $action" "apt-layer" + return 1 + ;; + esac +} + +# rpm-ostree usroverlay compatibility +rpm_ostree_usroverlay() { + local action="${1:-}" + + log_info "rpm-ostree usroverlay compatibility: $action" "apt-layer" + + case "$action" in + --mount) + mount_usr_overlay + ;; + --unmount) + unmount_usr_overlay + ;; + --status) + usr_overlay_status + ;; + *) + log_error "Invalid usroverlay action: $action" "apt-layer" + return 1 + ;; + esac +} + +# rpm-ostree composefs compatibility +rpm_ostree_composefs() { + local action="${1:-}" + shift + + log_info "rpm-ostree composefs compatibility: $action" "apt-layer" + + case "$action" in + --mount) + composefs_mount "$@" + ;; + --unmount) + composefs_unmount "$@" + ;; + --list) + composefs_list_images + ;; + --info) + composefs_image_info "$@" + ;; + *) + log_error "Invalid composefs action: $action" "apt-layer" + return 1 + ;; + esac +} + +# Helper functions for rpm-ostree compatibility + +# Get previous deployment +get_previous_deployment() { + local current_deployment + current_deployment=$(get_current_deployment) + + if [[ -n "$current_deployment" ]]; then + local parent_commit + parent_commit=$(jq -r ".deployments[\"$current_deployment\"].parent_commit" "$DEPLOYMENT_DB" 2>/dev/null || echo "") + echo "$parent_commit" + fi +} + +# Show package diff between commits +show_package_diff() { + local from_commit="$1" + local to_commit="$2" + + log_info "Showing package diff: $from_commit -> $to_commit" "apt-layer" + + # Get package lists from both commits + local from_packages=() + local to_packages=() + + if [[ -n "$from_commit" ]]; then + from_packages=($(get_packages_from_commit "$from_commit")) + fi + + if [[ -n "$to_commit" ]]; then + to_packages=($(get_packages_from_commit "$to_commit")) + fi + + # Calculate differences + local added_packages=() + local removed_packages=() + local updated_packages=() + + # Find added packages + for pkg in "${to_packages[@]}"; do + if [[ ! " ${from_packages[*]} " =~ " ${pkg} " ]]; then + added_packages+=("$pkg") + fi + done + + # Find removed packages + for pkg in "${from_packages[@]}"; do + if [[ ! " ${to_packages[*]} " =~ " ${pkg} " ]]; then + removed_packages+=("$pkg") + fi + done + + # Show results + if [[ ${#added_packages[@]} -gt 0 ]]; then + echo "Added packages:" + printf " %s\n" "${added_packages[@]}" + fi + + if [[ ${#removed_packages[@]} -gt 0 ]]; then + echo "Removed packages:" + printf " %s\n" "${removed_packages[@]}" + fi + + if [[ ${#added_packages[@]} -eq 0 ]] && [[ ${#removed_packages[@]} -eq 0 ]]; then + echo "No package changes detected" + fi +} + +# Get packages from commit +get_packages_from_commit() { + local commit_id="$1" + local composefs_image + + # Get ComposeFS image name + composefs_image=$(jq -r ".deployments[\"$commit_id\"].composefs_image" "$DEPLOYMENT_DB" 2>/dev/null || echo "") + + if [[ -z "$composefs_image" ]]; then + return 1 + fi + + # Mount and extract package list + local temp_mount="/tmp/apt-layer-commit-$$" + mkdir -p "$temp_mount" + + if composefs_mount "$composefs_image" "$temp_mount"; then + # Extract package list + chroot "$temp_mount" dpkg -l | grep '^ii' | awk '{print $2}' 2>/dev/null || true + + # Cleanup + composefs_unmount "$temp_mount" + rmdir "$temp_mount" + fi +} + +# Cleanup functions +cleanup_old_deployments() { + log_info "Cleaning up old deployments..." "apt-layer" + + # Keep last 5 deployments + local deployments + deployments=($(jq -r '.deployments | keys[]' "$DEPLOYMENT_DB" 2>/dev/null | sort -r | tail -n +6)) + + for commit_id in "${deployments[@]}"; do + log_info "Removing old deployment: $commit_id" "apt-layer" + + # Remove from database + jq --arg commit_id "$commit_id" 'del(.deployments[$commit_id])' \ + "$DEPLOYMENT_DB" > "${DEPLOYMENT_DB}.tmp" && mv "${DEPLOYMENT_DB}.tmp" "$DEPLOYMENT_DB" + + # Remove history file + rm -f "$DEPLOYMENT_HISTORY_DIR/$commit_id.json" + + # Remove deployment directory + rm -rf "/var/lib/particle-os/deployments/$commit_id" + done +} + +cleanup_old_composefs_images() { + log_info "Cleaning up old ComposeFS images..." "apt-layer" + + # Get list of images still referenced by deployments + local referenced_images + referenced_images=($(jq -r '.deployments[].composefs_image' "$DEPLOYMENT_DB" 2>/dev/null || true)) + + # Get all ComposeFS images + local all_images + all_images=($(composefs_list_images)) + + # Remove unreferenced images + for image in "${all_images[@]}"; do + if [[ ! " ${referenced_images[*]} " =~ " ${image} " ]]; then + log_info "Removing unreferenced image: $image" "apt-layer" + composefs_remove_image "$image" + fi + done +} + +cleanup_old_bootloader_entries() { + log_info "Cleaning up old bootloader entries..." "apt-layer" + + # Get current and pending deployments + local current_deployment + current_deployment=$(get_current_deployment) + local pending_deployment + pending_deployment=$(get_pending_deployment) + + # Remove old bootloader entries + local boot_dir="/boot/loader/entries" + for entry in "$boot_dir"/apt-layer-*.conf; do + if [[ -f "$entry" ]]; then + local commit_id + commit_id=$(basename "$entry" .conf | sed 's/apt-layer-//') + + # Keep current and pending deployments + if [[ "$commit_id" != "$current_deployment" ]] && [[ "$commit_id" != "$pending_deployment" ]]; then + log_info "Removing old bootloader entry: $entry" "apt-layer" + rm -f "$entry" + fi + fi + done +} + +# --- END OF SCRIPTLET: 10-rpm-ostree-compat.sh --- + +# ============================================================================ +# OSTree Atomic Package Management +# ============================================================================ +# OSTree Atomic Package Management - Implementation for apt-layer + +ostree_compose_install() { + local packages=("$@") + + # Validate input + if [[ ${#packages[@]} -eq 0 ]]; then + log_error "No packages specified for installation" "apt-layer" + log_info "Usage: apt-layer ostree compose install [...]" "apt-layer" + return 1 + fi + + log_info "[OSTree] Installing packages and creating atomic commit: ${packages[*]}" "apt-layer" + + # Check for root privileges + if [[ $EUID -ne 0 ]]; then + log_error "Root privileges required for OSTree compose install" "apt-layer" + return 1 + fi + + # Initialize workspace if needed + if ! init_workspace; then + log_error "Failed to initialize workspace" "apt-layer" + return 1 + fi + + # Start live overlay if not active + if ! is_live_overlay_active; then + log_info "[OSTree] Starting live overlay for package installation" "apt-layer" + if ! start_live_overlay; then + log_error "Failed to start live overlay" "apt-layer" + return 1 + fi + fi + + # Determine if .deb files or package names + local has_deb_files=false + for pkg in "${packages[@]}"; do + if [[ "$pkg" == *.deb ]] || [[ "$pkg" == */*.deb ]]; then + has_deb_files=true + break + fi + done + + # Install packages in live overlay + log_info "[OSTree] Installing packages in live overlay" "apt-layer" + if [[ "$has_deb_files" == "true" ]]; then + log_info "[OSTree] Detected .deb files, using live_dpkg_install" "apt-layer" + if ! live_dpkg_install "${packages[@]}"; then + log_error "Failed to install .deb packages in overlay" "apt-layer" + return 1 + fi + else + log_info "[OSTree] Detected package names, using live_install" "apt-layer" + if ! live_install "${packages[@]}"; then + log_error "Failed to install packages in overlay" "apt-layer" + return 1 + fi + fi + + # Create OSTree-style commit + local commit_message="Install packages: ${packages[*]}" + local commit_id="ostree-$(date +%Y%m%d-%H%M%S)-$$" + + log_info "[OSTree] Creating atomic commit: $commit_id" "apt-layer" + + # Create simple commit metadata (avoid complex JSON escaping) + local packages_json="[" + for i in "${!packages[@]}"; do + if [[ $i -gt 0 ]]; then + packages_json+="," + fi + packages_json+="\"${packages[$i]}\"" + done + packages_json+="]" + + local commit_data + commit_data=$(cat << EOF +{ + "commit_id": "$commit_id", + "type": "ostree_compose", + "action": "install", + "packages": $packages_json, + "parent_commit": "$(get_current_deployment)", + "commit_message": "Install packages: $(IFS=' '; echo "${packages[*]}")", + "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "composefs_image": "${commit_id}.composefs" +} +EOF +) + # Save commit metadata (for log/history) + local commit_log_dir="/var/lib/particle-os/ostree-commits" + mkdir -p "$commit_log_dir" + echo "$commit_data" > "$commit_log_dir/$commit_id.json" + + # Commit live overlay changes as new layer + log_info "[OSTree] Committing overlay changes as OSTree layer" "apt-layer" + if ! commit_live_overlay "$commit_message"; then + log_error "Failed to commit overlay changes" "apt-layer" + return 1 + fi + + # Get the created layer name (from commit_live_overlay) + local layer_name="live-overlay-commit-$(date +%Y%m%d_%H%M%S)" + + # Create OSTree deployment commit + log_info "[OSTree] Creating deployment commit with layer: $layer_name" "apt-layer" + local deployment_commit_id + deployment_commit_id=$(create_deployment_commit "ostree-base" "$layer_name") + + # Set as pending deployment (atomic) + set_pending_deployment "$deployment_commit_id" + + log_success "[OSTree] Atomic commit created successfully: $deployment_commit_id" "apt-layer" + log_info "[OSTree] Commit includes packages: ${packages[*]}" "apt-layer" + log_info "[OSTree] Reboot to activate the new deployment" "apt-layer" + + return 0 +} +ostree_compose_remove() { + local packages=("$@") + + # Validate input + if [[ ${#packages[@]} -eq 0 ]]; then + log_error "No packages specified for removal" "apt-layer" + log_info "Usage: apt-layer ostree compose remove [...]" "apt-layer" + return 1 + fi + + log_info "[OSTree] Removing packages and creating atomic commit: ${packages[*]}" "apt-layer" + + # Check for root privileges + if [[ $EUID -ne 0 ]]; then + log_error "Root privileges required for OSTree compose remove" "apt-layer" + return 1 + fi + + # Initialize workspace if needed + if ! init_workspace; then + log_error "Failed to initialize workspace" "apt-layer" + return 1 + fi + + # Start live overlay if not active + if ! is_live_overlay_active; then + log_info "[OSTree] Starting live overlay for package removal" "apt-layer" + if ! start_live_overlay; then + log_error "Failed to start live overlay" "apt-layer" + return 1 + fi + fi + + # Remove packages in live overlay + log_info "[OSTree] Removing packages in live overlay" "apt-layer" + if ! live_remove "${packages[@]}"; then + log_error "Failed to remove packages in overlay" "apt-layer" + return 1 + fi + + # Create OSTree-style commit + local commit_message="Remove packages: ${packages[*]}" + local commit_id="ostree-$(date +%Y%m%d-%H%M%S)-$$" + + log_info "[OSTree] Creating atomic commit: $commit_id" "apt-layer" + + # Create simple commit metadata + local packages_json="[" + for i in "${!packages[@]}"; do + if [[ $i -gt 0 ]]; then + packages_json+="," + fi + packages_json+="\"${packages[$i]}\"" + done + packages_json+="]" + + local commit_data + commit_data=$(cat << EOF +{ + "commit_id": "$commit_id", + "type": "ostree_compose", + "action": "remove", + "packages": $packages_json, + "parent_commit": "$(get_current_deployment)", + "commit_message": "Remove packages: $(IFS=' '; echo "${packages[*]}")", + "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "composefs_image": "${commit_id}.composefs" +} +EOF +) + # Save commit metadata (for log/history) + local commit_log_dir="/var/lib/particle-os/ostree-commits" + mkdir -p "$commit_log_dir" + echo "$commit_data" > "$commit_log_dir/$commit_id.json" + + # Commit live overlay changes as new layer + log_info "[OSTree] Committing overlay changes as OSTree layer" "apt-layer" + if ! commit_live_overlay "$commit_message"; then + log_error "Failed to commit overlay changes" "apt-layer" + return 1 + fi + + # Get the created layer name (from commit_live_overlay) + local layer_name="live-overlay-commit-$(date +%Y%m%d_%H%M%S)" + + # Create OSTree deployment commit + log_info "[OSTree] Creating deployment commit with layer: $layer_name" "apt-layer" + local deployment_commit_id + deployment_commit_id=$(create_deployment_commit "ostree-base" "$layer_name") + + # Set as pending deployment (atomic) + set_pending_deployment "$deployment_commit_id" + + log_success "[OSTree] Atomic commit created successfully: $deployment_commit_id" "apt-layer" + log_info "[OSTree] Commit includes removed packages: ${packages[*]}" "apt-layer" + log_info "[OSTree] Reboot to activate the new deployment" "apt-layer" + + return 0 +} +ostree_compose_update() { + local packages=("$@") + + # Validate input + if [[ ${#packages[@]} -eq 0 ]]; then + log_error "No packages specified for update" "apt-layer" + log_info "Usage: apt-layer ostree compose update [package1] [...]" "apt-layer" + log_info "Note: If no packages specified, updates all packages" "apt-layer" + return 1 + fi + + log_info "[OSTree] Updating packages and creating atomic commit: ${packages[*]}" "apt-layer" + + # Check for root privileges + if [[ $EUID -ne 0 ]]; then + log_error "Root privileges required for OSTree compose update" "apt-layer" + return 1 + fi + + # Initialize workspace if needed + if ! init_workspace; then + log_error "Failed to initialize workspace" "apt-layer" + return 1 + fi + + # Start live overlay if not active + if ! is_live_overlay_active; then + log_info "[OSTree] Starting live overlay for package update" "apt-layer" + if ! start_live_overlay; then + log_error "Failed to start live overlay" "apt-layer" + return 1 + fi + fi + + # Update packages in live overlay + log_info "[OSTree] Updating packages in live overlay" "apt-layer" + if ! live_update "${packages[@]}"; then + log_error "Failed to update packages in overlay" "apt-layer" + return 1 + fi + + # Create OSTree-style commit + local commit_message="Update packages: ${packages[*]}" + local commit_id="ostree-$(date +%Y%m%d-%H%M%S)-$$" + + log_info "[OSTree] Creating atomic commit: $commit_id" "apt-layer" + + # Create simple commit metadata + local packages_json="[" + for i in "${!packages[@]}"; do + if [[ $i -gt 0 ]]; then + packages_json+="," + fi + packages_json+="\"${packages[$i]}\"" + done + packages_json+="]" + + local commit_data + commit_data=$(cat << EOF +{ + "commit_id": "$commit_id", + "type": "ostree_compose", + "action": "update", + "packages": $packages_json, + "parent_commit": "$(get_current_deployment)", + "commit_message": "Update packages: $(IFS=' '; echo "${packages[*]}")", + "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "composefs_image": "${commit_id}.composefs" +} +EOF +) + # Save commit metadata (for log/history) + local commit_log_dir="/var/lib/particle-os/ostree-commits" + mkdir -p "$commit_log_dir" + echo "$commit_data" > "$commit_log_dir/$commit_id.json" + + # Commit live overlay changes as new layer + log_info "[OSTree] Committing overlay changes as OSTree layer" "apt-layer" + if ! commit_live_overlay "$commit_message"; then + log_error "Failed to commit overlay changes" "apt-layer" + return 1 + fi + + # Get the created layer name (from commit_live_overlay) + local layer_name="live-overlay-commit-$(date +%Y%m%d_%H%M%S)" + + # Create OSTree deployment commit + log_info "[OSTree] Creating deployment commit with layer: $layer_name" "apt-layer" + local deployment_commit_id + deployment_commit_id=$(create_deployment_commit "ostree-base" "$layer_name") + + # Set as pending deployment (atomic) + set_pending_deployment "$deployment_commit_id" + + log_success "[OSTree] Atomic commit created successfully: $deployment_commit_id" "apt-layer" + log_info "[OSTree] Commit includes updated packages: ${packages[*]}" "apt-layer" + log_info "[OSTree] Reboot to activate the new deployment" "apt-layer" + + return 0 +} +ostree_log() { + local format="${1:-full}" + local limit="${2:-10}" + + log_info "[OSTree] Showing commit log (format: $format, limit: $limit)" "apt-layer" + + if [[ ! -f "$DEPLOYMENT_DB" ]]; then + log_error "No deployment database found" "apt-layer" + return 1 + fi + + case "$format" in + "full"|"detailed") + echo "=== OSTree Commit Log ===" + jq -r --arg limit "$limit" ' + .deployments | to_entries | + sort_by(.value.created) | reverse | + .[0:($limit | tonumber)] | .[] | + "Commit: " + .key + "\n" + + "Message: " + (.value.commit_message // "unknown") + "\n" + + "Type: " + (.value.type // "unknown") + "\n" + + "Action: " + (.value.action // "unknown") + "\n" + + "Created: " + (.value.created // "unknown") + "\n" + + "Base: " + (.value.base_image // "unknown") + "\n" + + "Layers: " + (.value.layers | join(", ") // "none") + "\n" + + "Packages: " + (.value.packages | join(", ") // "none") + "\n" + + "---" + ' "$DEPLOYMENT_DB" 2>/dev/null || echo "No commits found" + ;; + "short"|"compact") + echo "=== OSTree Commit Log (Compact) ===" + jq -r --arg limit "$limit" ' + .deployments | to_entries | + sort_by(.value.created) | reverse | + .[0:($limit | tonumber)] | .[] | + "\(.key) - \(.value.commit_message // "unknown") (\(.value.created // "unknown"))" + ' "$DEPLOYMENT_DB" 2>/dev/null || echo "No commits found" + ;; + "json") + echo "=== OSTree Commit Log (JSON) ===" + jq -r --arg limit "$limit" ' + .deployments | to_entries | + sort_by(.value.created) | reverse | + .[0:($limit | tonumber)] | + map({commit_id: .key, details: .value}) + ' "$DEPLOYMENT_DB" 2>/dev/null || echo "[]" + ;; + *) + log_error "Invalid format: $format. Use: full, short, or json" "apt-layer" + return 1 + ;; + esac +} +ostree_diff() { + local commit1="${1:-}" + local commit2="${2:-}" + + log_info "[OSTree] Showing diff between commits" "apt-layer" + + if [[ ! -f "$DEPLOYMENT_DB" ]]; then + log_error "No deployment database found" "apt-layer" + return 1 + fi + + # If no commits specified, show diff between current and previous + if [[ -z "$commit1" ]]; then + local current_deployment + current_deployment=$(get_current_deployment) + if [[ -z "$current_deployment" ]]; then + log_error "No current deployment found" "apt-layer" + return 1 + fi + + # Get the commit before current + commit1=$(jq -r --arg current "$current_deployment" ' + .deployments | to_entries | + sort_by(.value.created) | + map(.key) | + index($current) as $idx | + if $idx > 0 then .[$idx - 1] else null end + ' "$DEPLOYMENT_DB" 2>/dev/null) + + if [[ -z "$commit1" || "$commit1" == "null" ]]; then + log_error "No previous commit found" "apt-layer" + return 1 + fi + + commit2="$current_deployment" + log_info "[OSTree] Comparing $commit1 -> $commit2" "apt-layer" + elif [[ -z "$commit2" ]]; then + # If only one commit specified, compare with current + local current_deployment + current_deployment=$(get_current_deployment) + if [[ -z "$current_deployment" ]]; then + log_error "No current deployment found" "apt-layer" + return 1 + fi + commit2="$current_deployment" + fi + + # Validate commits exist + if ! jq -e ".deployments[\"$commit1\"]" "$DEPLOYMENT_DB" >/dev/null 2>&1; then + log_error "Commit not found: $commit1" "apt-layer" + return 1 + fi + + if ! jq -e ".deployments[\"$commit2\"]" "$DEPLOYMENT_DB" >/dev/null 2>&1; then + log_error "Commit not found: $commit2" "apt-layer" + return 1 + fi + + # Get commit data + local commit1_data + commit1_data=$(jq -r ".deployments[\"$commit1\"]" "$DEPLOYMENT_DB") + local commit2_data + commit2_data=$(jq -r ".deployments[\"$commit2\"]" "$DEPLOYMENT_DB") + + echo "=== OSTree Diff: $commit1 -> $commit2 ===" + echo "" + + # Compare commit messages + local msg1 + msg1=$(echo "$commit1_data" | jq -r '.commit_message // "unknown"') + local msg2 + msg2=$(echo "$commit2_data" | jq -r '.commit_message // "unknown"') + echo "Commit Messages:" + echo " $commit1: $msg1" + echo " $commit2: $msg2" + echo "" + + # Compare creation times + local time1 + time1=$(echo "$commit1_data" | jq -r '.created // "unknown"') + local time2 + time2=$(echo "$commit2_data" | jq -r '.created // "unknown"') + echo "Creation Times:" + echo " $commit1: $time1" + echo " $commit2: $time2" + echo "" + + # Compare layers + local layers1 + layers1=$(echo "$commit1_data" | jq -r '.layers | join(", ") // "none"') + local layers2 + layers2=$(echo "$commit2_data" | jq -r '.layers | join(", ") // "none"') + echo "Layers:" + echo " $commit1: $layers1" + echo " $commit2: $layers2" + echo "" + + # Compare packages (if available) + local packages1 + packages1=$(echo "$commit1_data" | jq -r '.packages | join(", ") // "none"' 2>/dev/null || echo "none") + local packages2 + packages2=$(echo "$commit2_data" | jq -r '.packages | join(", ") // "none"' 2>/dev/null || echo "none") + echo "Packages:" + echo " $commit1: $packages1" + echo " $commit2: $packages2" + echo "" + + # Show action type + local action1 + action1=$(echo "$commit1_data" | jq -r '.action // "unknown"') + local action2 + action2=$(echo "$commit2_data" | jq -r '.action // "unknown"') + echo "Actions:" + echo " $commit1: $action1" + echo " $commit2: $action2" + echo "" + + # Calculate time difference + if [[ "$time1" != "unknown" && "$time2" != "unknown" ]]; then + local time_diff + time_diff=$(($(date -d "$time2" +%s) - $(date -d "$time1" +%s))) + echo "Time Difference: $time_diff seconds" + echo "" + fi + + return 0 +} +ostree_rollback() { + local target_commit="${1:-}" + + log_info "[OSTree] Rolling back deployment" "apt-layer" + + # Check for root privileges + if [[ $EUID -ne 0 ]]; then + log_error "Root privileges required for OSTree rollback" "apt-layer" + return 1 + fi + + # Get current deployment + local current_deployment + current_deployment=$(get_current_deployment) + + if [[ -z "$current_deployment" ]]; then + log_error "No current deployment found" "apt-layer" + return 1 + fi + + # If no target specified, rollback to previous commit + if [[ -z "$target_commit" ]]; then + log_info "[OSTree] No target specified, rolling back to previous commit" "apt-layer" + + # Get the commit before current + target_commit=$(jq -r --arg current "$current_deployment" ' + .deployments | to_entries | + sort_by(.value.created) | + map(.key) | + index($current) as $idx | + if $idx > 0 then .[$idx - 1] else null end + ' "$DEPLOYMENT_DB" 2>/dev/null) + + if [[ -z "$target_commit" || "$target_commit" == "null" ]]; then + log_error "No previous commit found to rollback to" "apt-layer" + return 1 + fi + + log_info "[OSTree] Rolling back to: $target_commit" "apt-layer" + fi + + # Validate target commit exists + if ! jq -e ".deployments[\"$target_commit\"]" "$DEPLOYMENT_DB" >/dev/null 2>&1; then + log_error "Target commit not found: $target_commit" "apt-layer" + return 1 + fi + + # Create rollback commit + local rollback_id="rollback-$(date +%Y%m%d-%H%M%S)-$$" + local rollback_message="Rollback from $current_deployment to $target_commit" + + log_info "[OSTree] Creating rollback commit: $rollback_id" "apt-layer" + + # Get target commit data + local target_data + target_data=$(jq -r ".deployments[\"$target_commit\"]" "$DEPLOYMENT_DB") + local base_image + base_image=$(echo "$target_data" | jq -r '.base_image') + local layers + layers=$(echo "$target_data" | jq -r '.layers | join(" ")') + + # Create rollback deployment commit + local rollback_commit_id + rollback_commit_id=$(create_deployment_commit "$base_image" $layers) + + # Set as pending deployment + set_pending_deployment "$rollback_commit_id" + + log_success "[OSTree] Rollback prepared successfully" "apt-layer" + log_info "[OSTree] Rollback from: $current_deployment" "apt-layer" + log_info "[OSTree] Rollback to: $target_commit" "apt-layer" + log_info "[OSTree] New deployment: $rollback_commit_id" "apt-layer" + log_info "[OSTree] Reboot to activate rollback" "apt-layer" + + return 0 +} +ostree_status() { + log_info "[OSTree] Showing current deployment status" "apt-layer" + + # Get current and pending deployments + local current_deployment + current_deployment=$(get_current_deployment) + local pending_deployment + pending_deployment=$(get_pending_deployment 2>/dev/null | tail -n1) + + echo "=== OSTree Deployment Status ===" + echo "Current Deployment: ${current_deployment:-none}" + echo "Pending Deployment: ${pending_deployment:-none}" + echo "" + + # Show recent commits (last 5) + echo "=== Recent Commits ===" + if [[ -f "$DEPLOYMENT_DB" ]]; then + jq -r '.deployments | to_entries | sort_by(.value.created) | reverse | .[0:5] | .[] | "\(.key) - \(.value.commit_message) (\(.value.created))"' "$DEPLOYMENT_DB" 2>/dev/null || echo "No commits found" + else + echo "No deployment database found" + fi + echo "" + + # Show layer information for current deployment + if [[ -n "$current_deployment" ]]; then + echo "=== Current Deployment Details ===" + local commit_data + commit_data=$(jq -r ".deployments[\"$current_deployment\"]" "$DEPLOYMENT_DB" 2>/dev/null) + if [[ -n "$commit_data" ]]; then + echo "Base Image: $(echo "$commit_data" | jq -r '.base_image // "unknown"')" + echo "Layers: $(echo "$commit_data" | jq -r '.layers | join(", ") // "none"')" + echo "Created: $(echo "$commit_data" | jq -r '.created // "unknown"')" + fi + fi + echo "" + + # Show available layers + echo "=== Available Layers ===" + if [[ -d "/var/lib/particle-os/build" ]]; then + find /var/lib/particle-os/build -name "*.squashfs" -type f | head -10 | while read -r layer; do + local size + size=$(du -h "$layer" | cut -f1) + local name + name=$(basename "$layer") + echo "$name ($size)" + done + else + echo "No layers found" + fi +} +ostree_cleanup() { + local keep_count="${1:-5}" + local dry_run="${2:-false}" + + log_info "[OSTree] Cleaning up old commits (keeping $keep_count)" "apt-layer" + + # Check for root privileges + if [[ $EUID -ne 0 ]]; then + log_error "Root privileges required for OSTree cleanup" "apt-layer" + return 1 + fi + + if [[ ! -f "$DEPLOYMENT_DB" ]]; then + log_error "No deployment database found" "apt-layer" + return 1 + fi + + # Get current and pending deployments (never delete these) + local current_deployment + current_deployment=$(get_current_deployment) + local pending_deployment + pending_deployment=$(get_pending_deployment) + + # Get commits to keep (most recent + current + pending) + local keep_commits + keep_commits=$(jq -r --arg keep "$keep_count" --arg current "$current_deployment" --arg pending "$pending_deployment" ' + .deployments | to_entries | + sort_by(.value.created) | reverse | + .[0:($keep | tonumber)] | + map(.key) + + if $current != "" then [$current] else [] end + + if $pending != "" and $pending != $current then [$pending] else [] end | + unique | join(" ") + ' "$DEPLOYMENT_DB" 2>/dev/null) + + # Get all commits + local all_commits + all_commits=$(jq -r '.deployments | keys | join(" ")' "$DEPLOYMENT_DB" 2>/dev/null) + + # Find commits to delete + local to_delete=() + for commit in $all_commits; do + if [[ ! " $keep_commits " =~ " $commit " ]]; then + to_delete+=("$commit") + fi + done + + if [[ ${#to_delete[@]} -eq 0 ]]; then + log_info "[OSTree] No commits to clean up" "apt-layer" + return 0 + fi + + echo "=== OSTree Cleanup Summary ===" + echo "Keeping commits: $keep_commits" + echo "Commits to delete: ${to_delete[*]}" + echo "Total to delete: ${#to_delete[@]}" + echo "" + + if [[ "$dry_run" == "true" ]]; then + log_info "[OSTree] Dry run - no changes made" "apt-layer" + return 0 + fi + + # Confirm deletion + echo "Are you sure you want to delete these commits? (y/N)" + read -r response + if [[ ! "$response" =~ ^[Yy]$ ]]; then + log_info "[OSTree] Cleanup cancelled" "apt-layer" + return 0 + fi + + # Delete commits + local deleted_count=0 + for commit in "${to_delete[@]}"; do + log_info "[OSTree] Deleting commit: $commit" "apt-layer" + + # Remove from database + jq --arg commit "$commit" 'del(.deployments[$commit])' "$DEPLOYMENT_DB" > "${DEPLOYMENT_DB}.tmp" && mv "${DEPLOYMENT_DB}.tmp" "$DEPLOYMENT_DB" + + # Remove history file + rm -f "$DEPLOYMENT_HISTORY_DIR/$commit.json" + + # Remove associated layers (if not used by other commits) + local commit_data + commit_data=$(jq -r ".deployments[\"$commit\"]" "$DEPLOYMENT_DB" 2>/dev/null) + if [[ -n "$commit_data" ]]; then + local layers + layers=$(echo "$commit_data" | jq -r '.layers[]?' 2>/dev/null) + for layer in $layers; do + # Check if layer is used by other commits + local layer_used + layer_used=$(jq -r --arg layer "$layer" ' + .deployments | to_entries | + map(select(.value.layers | contains([$layer]))) | + length + ' "$DEPLOYMENT_DB" 2>/dev/null) + + if [[ "$layer_used" == "0" ]]; then + log_info "[OSTree] Removing unused layer: $layer" "apt-layer" + rm -f "/var/lib/particle-os/build/$layer.squashfs" + fi + done + fi + + ((deleted_count++)) + done + + log_success "[OSTree] Cleanup completed: $deleted_count commits deleted" "apt-layer" + return 0 +} + +# --- END OF SCRIPTLET: 15-ostree-atomic.sh --- + +# ============================================================================ +# Direct dpkg Installation (Performance Optimization) +# ============================================================================ +# Direct dpkg installation for Particle-OS apt-layer Tool +# Provides faster, more controlled package installation using dpkg directly + +# Direct dpkg installation function +dpkg_direct_install() { + local packages=("$@") + local chroot_dir="${DPKG_CHROOT_DIR:-}" + local download_only="${DPKG_DOWNLOAD_ONLY:-false}" + local force_depends="${DPKG_FORCE_DEPENDS:-false}" + + log_info "Direct dpkg installation: ${packages[*]}" "apt-layer" + + # Create temporary directory for package downloads + local temp_dir + temp_dir=$(mktemp -d "${WORKSPACE}/temp/dpkg-install-XXXXXX") + + # Start transaction + start_transaction "dpkg_direct_install" + + # Download packages + update_transaction_phase "downloading_packages" + log_info "Downloading packages to: $temp_dir" "apt-layer" + + local download_cmd="apt-get download" + if [[ -n "$chroot_dir" ]]; then + download_cmd="chroot '$chroot_dir' apt-get download" + fi + + if ! eval "$download_cmd ${packages[*]}"; then + log_error "Failed to download packages" "apt-layer" + rollback_transaction + rm -rf "$temp_dir" + return 1 + fi + + # If download-only mode, return here + if [[ "$download_only" == "true" ]]; then + log_info "Download-only mode: packages saved to $temp_dir" "apt-layer" + commit_transaction + return 0 + fi + + # Get list of downloaded .deb files + local deb_files=() + while IFS= read -r -d '' file; do + deb_files+=("$file") + done < <(find "$temp_dir" -name "*.deb" -print0) + + if [[ ${#deb_files[@]} -eq 0 ]]; then + log_error "No .deb files found in download directory" "apt-layer" + rollback_transaction + rm -rf "$temp_dir" + return 1 + fi + + log_info "Downloaded ${#deb_files[@]} package files" "apt-layer" + + # Install packages using dpkg + update_transaction_phase "installing_packages" + log_info "Installing packages with dpkg..." "apt-layer" + + local dpkg_cmd="dpkg -i" + if [[ -n "$chroot_dir" ]]; then + dpkg_cmd="chroot '$chroot_dir' dpkg -i" + # Copy .deb files to chroot + cp "${deb_files[@]}" "$chroot_dir/tmp/" + deb_files=("${deb_files[@]/$temp_dir/$chroot_dir/tmp}") + fi + + # Add force-depends if requested + if [[ "$force_depends" == "true" ]]; then + dpkg_cmd="$dpkg_cmd --force-depends" + fi + + # Install packages + if ! eval "$dpkg_cmd ${deb_files[*]}"; then + log_warning "dpkg installation had issues, attempting dependency resolution" "apt-layer" + + # Try to fix broken dependencies + local fix_cmd="apt-get install -f" + if [[ -n "$chroot_dir" ]]; then + fix_cmd="chroot '$chroot_dir' apt-get install -f" + fi + + if ! eval "$fix_cmd"; then + log_error "Failed to resolve dependencies after dpkg installation" "apt-layer" + rollback_transaction + rm -rf "$temp_dir" + return 1 + fi + fi + + # Configure packages + update_transaction_phase "configuring_packages" + log_info "Configuring packages..." "apt-layer" + + local configure_cmd="dpkg --configure -a" + if [[ -n "$chroot_dir" ]]; then + configure_cmd="chroot '$chroot_dir' dpkg --configure -a" + fi + + if ! eval "$configure_cmd"; then + log_warning "Package configuration had issues" "apt-layer" + fi + + # Clean up + rm -rf "$temp_dir" + if [[ -n "$chroot_dir" ]]; then + rm -f "$chroot_dir"/tmp/*.deb + fi + + commit_transaction + log_success "Direct dpkg installation completed: ${packages[*]}" "apt-layer" + return 0 +} + +# Container-based dpkg installation +container_dpkg_install() { + local base_image="$1" + local new_image="$2" + local packages=("${@:3}") + + log_info "Container-based dpkg installation: ${packages[*]}" "apt-layer" + + # Create temporary container name + local container_name="apt-layer-dpkg-$(date +%s)-$$" + local temp_dir="$WORKSPACE/temp/$container_name" + + # Ensure temp directory exists + mkdir -p "$temp_dir" + + # Start transaction + start_transaction "container-dpkg-install-$container_name" + + # Use existing container creation function if available, otherwise create base image + if command -v create_base_container_image >/dev/null 2>&1; then + if ! create_base_container_image "$base_image" "$container_name"; then + rollback_transaction + return 1 + fi + else + # Fallback: create base image directory + log_info "Using fallback container image creation" "apt-layer" + if [[ -d "$WORKSPACE/images/$base_image" ]]; then + cp -a "$WORKSPACE/images/$base_image" "$temp_dir" + else + log_error "Base image not found: $base_image" "apt-layer" + rollback_transaction + return 1 + fi + fi + + # Run dpkg installation in container + case "$CONTAINER_RUNTIME" in + podman) + if ! run_podman_dpkg_install "$base_image" "$container_name" "$temp_dir" "${packages[@]}"; then + rollback_transaction + return 1 + fi + ;; + docker) + if ! run_docker_dpkg_install "$base_image" "$container_name" "$temp_dir" "${packages[@]}"; then + rollback_transaction + return 1 + fi + ;; + systemd-nspawn) + if ! run_nspawn_dpkg_install "$base_image" "$container_name" "$temp_dir" "${packages[@]}"; then + rollback_transaction + return 1 + fi + ;; + *) + log_error "Unsupported container runtime: $CONTAINER_RUNTIME" "apt-layer" + rollback_transaction + return 1 + ;; + esac + + # Create ComposeFS layer from container changes + if command -v create_composefs_layer >/dev/null 2>&1; then + if ! create_composefs_layer "$temp_dir" "$new_image"; then + rollback_transaction + return 1 + fi + else + # Fallback: use composefs-alternative.sh + log_info "Using fallback ComposeFS layer creation" "apt-layer" + if ! "$COMPOSEFS_SCRIPT" create "$new_image" "$temp_dir"; then + log_error "Failed to create ComposeFS layer" "apt-layer" + rollback_transaction + return 1 + fi + fi + + # Commit transaction + commit_transaction + + # Cleanup + if command -v cleanup_container_artifacts >/dev/null 2>&1; then + cleanup_container_artifacts "$container_name" "$temp_dir" + else + # Fallback cleanup + rm -rf "$temp_dir" + fi + + log_success "Container-based dpkg installation completed" "apt-layer" + return 0 +} + +# Podman-based dpkg installation +run_podman_dpkg_install() { + local base_image="$1" + local container_name="$2" + local temp_dir="$3" + shift 3 + local packages=("$@") + + log_info "Running podman-based dpkg installation" "apt-layer" + + # Create container from base image + local container_id + if [[ -d "$WORKSPACE/images/$base_image" ]]; then + # Use ComposeFS image as base + container_id=$(podman create --name "$container_name" \ + --mount type=bind,source="$WORKSPACE/images/$base_image",target=/ \ + --mount type=bind,source="$temp_dir",target=/output \ + ubuntu:24.04 /bin/bash) + else + # Use standard Ubuntu image + container_id=$(podman create --name "$container_name" \ + --mount type=bind,source="$temp_dir",target=/output \ + ubuntu:24.04 /bin/bash) + fi + + if [[ -z "$container_id" ]]; then + log_error "Failed to create podman container" "apt-layer" + return 1 + fi + + # Start container and install packages + if ! podman start "$container_name"; then + log_error "Failed to start podman container" "apt-layer" + podman rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Download and install packages using dpkg + local install_cmd=" + apt-get update && + apt-get download ${packages[*]} && + dpkg -i *.deb && + apt-get install -f && + dpkg --configure -a && + apt-get clean + " + + if ! podman exec "$container_name" /bin/bash -c "$install_cmd"; then + log_error "dpkg installation failed in podman container" "apt-layer" + podman stop "$container_name" 2>/dev/null || true + podman rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Export container filesystem + if ! podman export "$container_name" | tar -x -C "$temp_dir"; then + log_error "Failed to export podman container filesystem" "apt-layer" + podman stop "$container_name" 2>/dev/null || true + podman rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Cleanup container + podman stop "$container_name" 2>/dev/null || true + podman rm "$container_name" 2>/dev/null || true + + log_success "Podman-based dpkg installation completed" "apt-layer" + return 0 +} + +# Docker-based dpkg installation +run_docker_dpkg_install() { + local base_image="$1" + local container_name="$2" + local temp_dir="$3" + shift 3 + local packages=("$@") + + log_info "Running docker-based dpkg installation" "apt-layer" + + # Create container from base image + local container_id + if [[ -d "$WORKSPACE/images/$base_image" ]]; then + # Use ComposeFS image as base + container_id=$(docker create --name "$container_name" \ + -v "$WORKSPACE/images/$base_image:/" \ + -v "$temp_dir:/output" \ + ubuntu:24.04 /bin/bash) + else + # Use standard Ubuntu image + container_id=$(docker create --name "$container_name" \ + -v "$temp_dir:/output" \ + ubuntu:24.04 /bin/bash) + fi + + if [[ -z "$container_id" ]]; then + log_error "Failed to create docker container" "apt-layer" + return 1 + fi + + # Start container and install packages + if ! docker start "$container_name"; then + log_error "Failed to start docker container" "apt-layer" + docker rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Download and install packages using dpkg + local install_cmd=" + apt-get update && + apt-get download ${packages[*]} && + dpkg -i *.deb && + apt-get install -f && + dpkg --configure -a && + apt-get clean + " + + if ! docker exec "$container_name" /bin/bash -c "$install_cmd"; then + log_error "dpkg installation failed in docker container" "apt-layer" + docker stop "$container_name" 2>/dev/null || true + docker rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Export container filesystem + if ! docker export "$container_name" | tar -x -C "$temp_dir"; then + log_error "Failed to export docker container filesystem" "apt-layer" + docker stop "$container_name" 2>/dev/null || true + docker rm "$container_name" 2>/dev/null || true + return 1 + fi + + # Cleanup container + docker stop "$container_name" 2>/dev/null || true + docker rm "$container_name" 2>/dev/null || true + + log_success "Docker-based dpkg installation completed" "apt-layer" + return 0 +} + +# systemd-nspawn-based dpkg installation +run_nspawn_dpkg_install() { + local base_image="$1" + local container_name="$2" + local temp_dir="$3" + shift 3 + local packages=("$@") + + log_info "Running systemd-nspawn-based dpkg installation" "apt-layer" + + local container_dir="$WORKSPACE/containers/$container_name" + + # Create container directory + if [[ -d "$WORKSPACE/images/$base_image" ]]; then + # Use ComposeFS image as base + log_info "Using ComposeFS image as base for nspawn" "apt-layer" + cp -a "$WORKSPACE/images/$base_image" "$container_dir" + else + # Use host filesystem as base + log_info "Using host filesystem as base for nspawn" "apt-layer" + # Create minimal container structure + mkdir -p "$container_dir"/{bin,lib,lib64,usr,etc,var} + # Copy essential files from host + cp -a /bin/bash "$container_dir/bin/" + cp -a /lib/x86_64-linux-gnu "$container_dir/lib/" + cp -a /usr/bin/dpkg "$container_dir/usr/bin/" + cp -a /usr/bin/apt-get "$container_dir/usr/bin/" + # Add minimal /etc structure + echo "deb http://archive.ubuntu.com/ubuntu/ jammy main" > "$container_dir/etc/apt/sources.list" + fi + + # Run dpkg installation in nspawn container + local install_cmd=" + apt-get update && + apt-get download ${packages[*]} && + dpkg -i *.deb && + apt-get install -f && + dpkg --configure -a && + apt-get clean + " + + if ! systemd-nspawn -D "$container_dir" /bin/bash -c "$install_cmd"; then + log_error "dpkg installation failed in nspawn container" "apt-layer" + return 1 + fi + + # Move container contents to temp_dir + mv "$container_dir"/* "$temp_dir/" 2>/dev/null || true + + log_success "systemd-nspawn-based dpkg installation completed" "apt-layer" + return 0 +} + +# Live overlay dpkg installation +live_dpkg_install() { + local packages=("$@") + + log_info "Installing packages in live overlay with dpkg: ${packages[*]}" "apt-layer" + + # Check if overlay is active + if command -v is_live_overlay_active >/dev/null 2>&1; then + if ! is_live_overlay_active; then + log_error "Live overlay is not active" "apt-layer" + log_info "Use '--live-overlay start' to start live overlay first" "apt-layer" + return 1 + fi + else + # Fallback: check if overlay variables are set + if [[ -z "${LIVE_OVERLAY_MOUNT_POINT:-}" ]]; then + log_error "Live overlay system not available" "apt-layer" + log_info "Live overlay functionality requires overlayfs support" "apt-layer" + return 1 + fi + fi + + # Check for root privileges + if [[ $EUID -ne 0 ]]; then + log_error "Root privileges required for live installation" "apt-layer" + return 1 + fi + + # Check if we're dealing with .deb files or package names + local has_deb_files=false + for package in "${packages[@]}"; do + if [[ "$package" == *.deb ]]; then + has_deb_files=true + break + fi + done + + if [[ "$has_deb_files" == "true" ]]; then + # Install .deb files directly + install_deb_files_in_overlay "${packages[@]}" + else + # Download and install packages using apt-get + dpkg + install_packages_in_overlay "${packages[@]}" + fi +} + +# Install .deb files directly in overlay +install_deb_files_in_overlay() { + local deb_files=("$@") + + log_info "Installing .deb files directly in overlay: ${deb_files[*]}" "apt-layer" + + # Create temporary directory in overlay for .deb files + local overlay_temp_dir="$LIVE_OVERLAY_MOUNT_POINT/tmp/apt-layer-debs" + mkdir -p "$overlay_temp_dir" + + # Copy .deb files to overlay + log_info "Copying .deb files to overlay" "apt-layer" + for deb_file in "${deb_files[@]}"; do + if [[ -f "$deb_file" ]]; then + cp "$deb_file" "$overlay_temp_dir/" + else + log_warning "File not found: $deb_file" "apt-layer" + fi + done + + # Install .deb files with dpkg + log_info "Installing .deb files with dpkg in overlay" "apt-layer" + local install_cmd=" + cd /tmp/apt-layer-debs && + dpkg -i *.deb && + apt-get install -f && + dpkg --configure -a && + apt-get clean + " + + if chroot "$LIVE_OVERLAY_MOUNT_POINT" /bin/bash -c "$install_cmd"; then + log_success "Packages installed successfully in overlay with dpkg" "apt-layer" + + # Log installed packages if log file is defined + if [[ -n "${LIVE_OVERLAY_PACKAGE_LOG:-}" ]]; then + for deb_file in "${deb_files[@]}"; do + local package_name=$(basename "$deb_file" .deb) + echo "$(date '+%Y-%m-%d %H:%M:%S') - INSTALLED: $package_name (dpkg)" >> "$LIVE_OVERLAY_PACKAGE_LOG" + done + fi + + # Clean up temporary directory + rm -rf "$overlay_temp_dir" + + log_info "Changes are applied to overlay and can be committed or rolled back" "apt-layer" + return 0 + else + log_error "Failed to install packages in overlay with dpkg" "apt-layer" + # Clean up temporary directory + rm -rf "$overlay_temp_dir" + return 1 + fi +} + +# Install packages by name in overlay (download + install) +install_packages_in_overlay() { + local packages=("$@") + + log_info "Downloading and installing packages in overlay: ${packages[*]}" "apt-layer" + + # Update package lists in overlay + log_info "Updating package lists in overlay" "apt-layer" + if ! chroot "$LIVE_OVERLAY_MOUNT_POINT" apt-get update; then + log_error "Failed to update package lists" "apt-layer" + return 1 + fi + + # Download and install packages using dpkg + log_info "Installing packages with dpkg in overlay" "apt-layer" + local install_cmd=" + apt-get download ${packages[*]} && + dpkg -i *.deb && + apt-get install -f && + dpkg --configure -a && + apt-get clean + " + + if chroot "$LIVE_OVERLAY_MOUNT_POINT" /bin/bash -c "$install_cmd"; then + log_success "Packages installed successfully in overlay with dpkg" "apt-layer" + + # Log installed packages if log file is defined + if [[ -n "${LIVE_OVERLAY_PACKAGE_LOG:-}" ]]; then + for package in "${packages[@]}"; do + echo "$(date '+%Y-%m-%d %H:%M:%S') - INSTALLED: $package (dpkg)" >> "$LIVE_OVERLAY_PACKAGE_LOG" + done + fi + + log_info "Changes are applied to overlay and can be committed or rolled back" "apt-layer" + return 0 + else + log_error "Failed to install packages in overlay with dpkg" "apt-layer" + return 1 + fi +} + +# Package verification using dpkg +verify_package_integrity() { + local package="$1" + + log_info "Verifying package integrity: $package" "apt-layer" + + # Check if package is installed + if ! dpkg -l "$package" >/dev/null 2>&1; then + log_error "Package '$package' is not installed" "apt-layer" + return 1 + fi + + # Verify package files + if ! dpkg -V "$package" >/dev/null 2>&1; then + log_warning "Package '$package' has file integrity issues" "apt-layer" + return 1 + fi + + # Check package status + local status + status=$(dpkg -s "$package" 2>/dev/null | grep "^Status:" | cut -d: -f2 | tr -d ' ') + + if [[ "$status" != "installokinstalled" ]]; then + log_warning "Package '$package' has status issues: $status" "apt-layer" + return 1 + fi + + log_success "Package '$package' integrity verified" "apt-layer" + return 0 +} + +# Batch package verification +verify_all_packages() { + local packages=("$@") + + log_info "Verifying integrity of ${#packages[@]} packages" "apt-layer" + + local failed_packages=() + + for package in "${packages[@]}"; do + if ! verify_package_integrity "$package"; then + failed_packages+=("$package") + fi + done + + if [[ ${#failed_packages[@]} -gt 0 ]]; then + log_warning "Found ${#failed_packages[@]} packages with integrity issues: ${failed_packages[*]}" "apt-layer" + return 1 + fi + + log_success "All packages verified successfully" "apt-layer" + return 0 +} + +# --- END OF SCRIPTLET: 24-dpkg-direct-install.sh --- + +# --- END OF SCRIPTLET: 24-dpkg-direct-install.sh --- + +# ============================================================================ +# Main Dispatch and Help +# ============================================================================ +# Main execution and command dispatch for Particle-OS apt-layer Tool + +# Show version information +show_version() { + cat << 'EOF' +apt-layer: + Version: '2025.1' + Git: Particle-OS apt-layer Tool + Compiled: 2025-01-27 23:55 UTC + Features: + - composefs + - container + - live-overlay + - rpm-ostree-compat + - atomic-transactions + - dpkg-direct-install +EOF +} + +# Show concise usage information +show_usage() { + cat << 'EOF' +Usage: + apt-layer [OPTION…] COMMAND + +Builtin Commands: + install Overlay additional packages + upgrade Perform a system upgrade + rebase Switch to a different base + rollback Revert to the previously booted deployment + status Get the version of the booted system + kargs Query or modify kernel arguments + cleanup Clear cached/pending data + cancel Cancel an active transaction + initramfs Enable or disable local initramfs regeneration + usroverlay Apply a transient overlayfs to /usr + +Layer Management: + --container Create layer using container isolation + --dpkg-install Install packages using direct dpkg + --live-install Install packages on live system + --live-overlay Manage live system overlayfs + --live-commit Commit live overlay changes + --live-rollback Rollback live overlay changes + + + +Image Management: + --list List available images + --info Show image information + --remove Remove image + --oci-export Export as OCI image + --oci-import Import OCI image + +System Management: + --init Initialize apt-layer system + --reinit Reinitialize apt-layer system (force recreation) + --rm-init Remove apt-layer system (cleanup) + --reset Reset apt-layer system + --status Show apt-layer system status + --help-full Show detailed help + --examples Show usage examples + +Help Options: + -h, --help Show help options + +Application Options: + --version Print version information and exit + -q, --quiet Avoid printing most informational messages + +Examples: + apt-layer ubuntu-base/24.04 gaming/24.04 steam wine + apt-layer --container ubuntu-base/24.04 dev/24.04 vscode git + apt-layer --live-install firefox + apt-layer install steam wine + apt-layer status +EOF +} + +# Show full detailed usage information +show_full_usage() { + cat << 'EOF' +apt-layer Tool - Enhanced with Container Support and LIVE SYSTEM LAYERING +Like rpm-ostree + Vanilla OS Apx for Ubuntu/Debian, now ComposeFS-based + +BASIC LAYER CREATION: + apt-layer base-image new-image [packages...] + # Add a new layer to an existing ComposeFS image (build or user) + + apt-layer --container base-image new-image [packages...] + # Create layer using container isolation (like Apx) + + apt-layer --dpkg-install packages + # Install packages using direct dpkg (faster, more controlled) + + apt-layer --container-dpkg base-image new-image [packages...] + # Create layer using container isolation with dpkg (optimized) + +LIVE SYSTEM LAYERING: + apt-layer --live-install packages + # Install packages on live system with overlayfs (like rpm-ostree install) + + apt-layer --live-dpkg packages + # Install packages on live system using dpkg (optimized) + + apt-layer --live-overlay action [options] + # Manage live system overlayfs + # Actions: start, stop, status, commit, rollback + + apt-layer --live-commit [message] + # Commit current live overlay changes as new ComposeFS layer + + apt-layer --live-rollback + # Rollback live overlay changes + +rpm-ostree COMPATIBILITY: + apt-layer install packages + # Install packages (rpm-ostree install compatibility) + + apt-layer upgrade + # Upgrade system (rpm-ostree upgrade compatibility) + + apt-layer rebase new-base + # Rebase to new base (rpm-ostree rebase compatibility) + + apt-layer rollback [commit] + # Rollback to previous deployment (rpm-ostree rollback compatibility) + + apt-layer status + # Show deployment status (rpm-ostree status compatibility) + + apt-layer diff [from] [to] + # Show package differences (rpm-ostree diff compatibility) + + apt-layer db list + # List deployments (rpm-ostree db list compatibility) + + apt-layer db diff [from] [to] + # Show detailed differences (rpm-ostree db diff compatibility) + + apt-layer cleanup [--purge] + # Clean up old deployments (rpm-ostree cleanup compatibility) + + apt-layer cancel + # Cancel pending deployment (rpm-ostree cancel compatibility) + + apt-layer initramfs action + # Manage initramfs (rpm-ostree initramfs compatibility) + + apt-layer kargs action [args...] + # Manage kernel arguments (rpm-ostree kargs compatibility) + + apt-layer bootloader action [options] + # Manage bootloader entries and configuration + + apt-layer usroverlay action + # Manage /usr overlay (rpm-ostree usroverlay compatibility) + + apt-layer composefs action [args...] + # Manage ComposeFS (rpm-ostree composefs compatibility) + +IMAGE MANAGEMENT: + apt-layer --list + # List all available ComposeFS images/layers + + apt-layer --info image + # Show information about a specific ComposeFS image/layer + + apt-layer --remove image + # Remove an image/layer + + apt-layer --oci-export image placeholder + # Export ComposeFS image as OCI image + + apt-layer --oci-import placeholder placeholder + # Import OCI image as ComposeFS image + + apt-layer --oci-status + # Show OCI integration system status + +SYSTEM MANAGEMENT: + apt-layer --init + # Initialize apt-layer system + + apt-layer --reset + # Reset apt-layer system + +EXAMPLES: + apt-layer ubuntu-base/24.04 gaming/24.04 steam wine + apt-layer --container ubuntu-base/24.04 dev/24.04 vscode git + apt-layer --dpkg-install curl wget + apt-layer --live-install firefox + apt-layer install steam wine + apt-layer status +EOF +} + +# Show category-specific help +show_layer_help() { + cat << 'EOF' +Layer Management Commands + +BASIC LAYER CREATION: + apt-layer base-image new-image [packages...] + # Create new layer from base image with packages + + apt-layer --container base-image new-image [packages...] + # Create layer using container isolation (like Apx) + + apt-layer --dpkg-install packages + # Install packages using direct dpkg (faster, more controlled) + + apt-layer --container-dpkg base-image new-image [packages...] + # Create layer using container isolation with dpkg (optimized) + + apt-layer --advanced-install packages + # Install packages with security checks and dependency resolution + + apt-layer --advanced-remove packages + # Remove packages with dependency checking and safety validation + + apt-layer --advanced-update packages + # Update packages with rollback capability and backup creation + +Examples: + apt-layer ubuntu-base/24.04 gaming/24.04 steam wine + apt-layer --container ubuntu-base/24.04 dev/24.04 vscode git + apt-layer --dpkg-install curl wget + apt-layer --advanced-install firefox +EOF +} + +show_live_help() { + cat << 'EOF' +Live System Management Commands + +LIVE INSTALLATION: + apt-layer --live-install packages + # Install packages on live system with overlayfs (like rpm-ostree install) + # Uses apt-get (requires network access) + # ⚠️ For WSL/offline/atomic overlays, use --live-dpkg instead + + apt-layer --live-dpkg packages + # Install packages on live system using dpkg (optimized for overlays, offline, WSL) + # Usage: apt-layer --live-dpkg /path/to/*.deb + +LIVE OVERLAY MANAGEMENT: + apt-layer --live-overlay action [options] + # Manage live system overlayfs + # Actions: start, stop, status, commit, rollback + + apt-layer --live-commit [message] + # Commit current live overlay changes as new ComposeFS layer + + apt-layer --live-rollback + # Rollback live overlay changes + +Examples: + apt-layer --live-install firefox + apt-layer --live-dpkg ~/apt-cache/*.deb + apt-layer --live-overlay start + apt-layer --live-overlay commit "Add development tools" + apt-layer --live-rollback +EOF +} + +show_rpm_ostree_help() { + cat << 'EOF' +rpm-ostree Compatibility Commands + +BASIC COMMANDS: + apt-layer install packages + # Install packages (rpm-ostree install compatibility) + + apt-layer upgrade + # Upgrade system (rpm-ostree upgrade compatibility) + + apt-layer rebase new-base + # Rebase to new base (rpm-ostree rebase compatibility) + + apt-layer rollback [commit] + # Rollback to previous deployment (rpm-ostree rollback compatibility) + + apt-layer status + # Show deployment status (rpm-ostree status compatibility) + + apt-layer diff [from] [to] + # Show package differences (rpm-ostree diff compatibility) + +DATABASE COMMANDS: + apt-layer db list + # List deployments (rpm-ostree db list compatibility) + + apt-layer db diff [from] [to] + # Show detailed differences (rpm-ostree db diff compatibility) + +SYSTEM COMMANDS: + apt-layer cleanup [--purge] + # Clean up old deployments (rpm-ostree cleanup compatibility) + + apt-layer cancel + # Cancel pending deployment (rpm-ostree cancel compatibility) + + apt-layer initramfs action + # Manage initramfs (rpm-ostree initramfs compatibility) + + apt-layer kargs action [args...] + # Manage kernel arguments (rpm-ostree kargs compatibility) + + apt-layer bootloader action [options] + # Manage bootloader entries and configuration + + apt-layer usroverlay action + # Manage /usr overlay (rpm-ostree usroverlay compatibility) + + apt-layer composefs action [args...] + # Manage ComposeFS (rpm-ostree composefs compatibility) + +Examples: + apt-layer install steam wine + apt-layer status + apt-layer upgrade + apt-layer kargs add "console=ttyS0" + apt-layer rollback +EOF +} + +# Show image management help +show_image_help() { + cat << 'EOF' +IMAGE MANAGEMENT COMMANDS: + +IMAGE OPERATIONS: + apt-layer --list + # List all available ComposeFS images/layers + + apt-layer --info image + # Show information about a specific ComposeFS image/layer + + apt-layer --remove image + # Remove an image/layer + +OCI INTEGRATION: + apt-layer --oci-export image placeholder + # Export ComposeFS image as OCI image + + apt-layer --oci-import placeholder placeholder + # Import OCI image as ComposeFS image + + apt-layer --oci-status + # Show OCI integration system status + +EXAMPLES: + apt-layer --list + apt-layer --info particle-os/base/24.04 + apt-layer --remove old-layer + apt-layer --oci-export my-image oci:my-registry/my-image:latest +EOF +} + +show_security_help() { + cat << 'EOF' +Security & Signing Commands + +LAYER SIGNING & VERIFICATION: + apt-layer --generate-key key-name type + # Generate signing key pair (sigstore, gpg) + + apt-layer --sign-layer layer-path key-name + # Sign layer with specified key + + apt-layer --verify-layer layer-path + # Verify layer signature + + apt-layer --revoke-layer layer-path [reason] + # Revoke layer (mark as untrusted) + + apt-layer --list-keys + # List all signing keys + + apt-layer --list-signatures + # List all layer signatures + + apt-layer --layer-status layer-path + # Show layer signing status + +SECURITY SCANNING: + apt-layer --scan-package package-name [version] [scan-level] + # Scan package for vulnerabilities (standard, thorough, quick) + + apt-layer --scan-layer layer-path [scan-level] + # Scan layer for vulnerabilities + + apt-layer --generate-security-report type [format] [scan-level] + # Generate security report (package, layer, system) + + apt-layer --security-status + # Show security scanning system status + + apt-layer --update-cve-database + # Update CVE database from NVD + + apt-layer --cleanup-security-reports [days] + # Clean up old security reports (default: 90 days) + +Examples: + apt-layer --generate-key my-key sigstore + apt-layer --sign-layer layer.squashfs my-key + apt-layer --verify-layer layer.squashfs + apt-layer --scan-package firefox + apt-layer --security-status +EOF +} + +show_audit_help() { + cat << 'EOF' +Audit & Compliance Commands + +AUDIT LOGGING: + apt-layer --query-audit format [filters...] + # Query audit logs with filters (json, csv, table) + + apt-layer --export-audit format [output-file] [filters...] + # Export audit logs to file (json, csv, html) + + apt-layer --list-audit-reports + # List all audit reports + + apt-layer --audit-status + # Show audit system status + + apt-layer --cleanup-audit-logs [days] + # Clean up old audit logs (default: 90 days) + +COMPLIANCE REPORTING: + apt-layer --generate-compliance-report framework [period] [format] + # Generate compliance report (sox, pci-dss) + +Examples: + apt-layer --query-audit json --user=admin --since=2024-01-01 + apt-layer --export-audit csv --output=audit-export.csv + apt-layer --generate-compliance-report sox monthly html + apt-layer --audit-status +EOF +} + +show_admin_help() { + cat << 'EOF' +Admin Utilities Commands + +SYSTEM HEALTH: + apt-layer admin health + # System health check and diagnostics + + apt-layer admin perf + # Performance analytics and resource usage + +MAINTENANCE: + apt-layer admin cleanup + # Maintenance cleanup + + apt-layer admin backup + # Backup configs and layers + + apt-layer admin restore + # Restore from backup + +USER MANAGEMENT: + apt-layer --add-user username role + # Add user to package management system with specified role + + apt-layer --remove-user username + # Remove user from package management system + + apt-layer --list-users + # List all package management users and roles + +PACKAGE MANAGEMENT: + apt-layer --package-info package + # Get detailed information about a package + + apt-layer --package-status + # Show advanced package management system status + + apt-layer --list-backups + # List all package backups + + apt-layer --cleanup-backups [days] + # Clean up backups older than specified days (default: 30) + +Examples: + apt-layer admin health + apt-layer admin perf + apt-layer --add-user john package_manager + apt-layer --list-users + apt-layer --package-status +EOF +} + +show_enterprise_help() { + cat << 'EOF' +Enterprise Features Commands + +MULTI-TENANT MANAGEMENT: + apt-layer tenant action [options] + # Multi-tenant management + # Actions: init, create, delete, list, info, quota, backup, restore, health + +COMPLIANCE FRAMEWORKS: + apt-layer compliance action [options] + # Compliance framework management + # Actions: init, enable, disable, list, scan, report + +ENTERPRISE INTEGRATION: + apt-layer enterprise action [options] + # Enterprise integration + # Actions: init, enable, disable, list, test, hook, send + +MONITORING & ALERTING: + apt-layer monitoring action [options] + # Monitoring and alerting + # Actions: init, check, policy, history, report + +Examples: + apt-layer tenant create my-org + apt-layer compliance enable SOX + apt-layer enterprise enable SIEM siem-config.json + apt-layer monitoring check +EOF +} + +show_cloud_help() { + cat << 'EOF' +Cloud Integration Commands + +CLOUD PROVIDERS: + apt-layer cloud action [options] + # Cloud provider integration (AWS, Azure, GCP) + # Actions: init, aws, azure, gcp, deploy, status, list-deployments, cleanup + +KUBERNETES: + apt-layer kubernetes action [options] + # Kubernetes integration (EKS, AKS, GKE, OpenShift) + # Actions: init, eks, aks, gke, openshift, deploy, helm, monitoring, security, cleanup + +CONTAINER ORCHESTRATION: + apt-layer orchestration action [options] + # Container orchestration + # Actions: init, multi-cluster, service-mesh, gitops, deployments, status, cleanup + +MULTI-CLOUD: + apt-layer multicloud action [options] + # Multi-cloud deployment + # Actions: init, add-profile, list-profiles, deploy, migrate, status, policy + +CLOUD SECURITY: + apt-layer cloud-security action [options] + # Cloud-native security + # Actions: init, scan, policy, list-scans, list-policies, cleanup, status + +Examples: + apt-layer cloud aws init + apt-layer cloud deploy particle-os/gaming/24.04 aws ecr + apt-layer kubernetes eks create-cluster my-cluster us-west-2 + apt-layer orchestration gitops init https://github.com/my-org/gitops-repo + apt-layer cloud-security scan particle-os/gaming/24.04 aws comprehensive +EOF +} + +# Show examples +show_examples() { + cat << 'EOF' +Particle-OS apt-layer Tool - Examples + +BASIC LAYER CREATION: + # Create gaming layer from base Ubuntu image + apt-layer particle-os/base/24.04 particle-os/gaming/24.04 steam wine + + # Create development layer with container isolation + apt-layer --container particle-os/base/24.04 particle-os/dev/24.04 vscode git + + # Direct dpkg installation (faster) + apt-layer --dpkg-install curl wget + +LIVE SYSTEM MANAGEMENT: + # Install packages on running system + apt-layer --live-install firefox + + # Start live overlay for temporary changes + apt-layer --live-overlay start + + # Commit overlay changes as new layer + apt-layer --live-overlay commit "Add development tools" + + # Rollback overlay changes + apt-layer --live-rollback + +rpm-ostree COMPATIBILITY: + # Install packages (rpm-ostree style) + apt-layer install steam wine + + # Check system status + apt-layer status + + # Upgrade system + apt-layer upgrade + + # Add kernel argument + apt-layer kargs add "console=ttyS0" + +IMAGE MANAGEMENT: + # List available images + apt-layer --list + + # Show image details + apt-layer --info particle-os/gaming/24.04 + + # Export as OCI image + apt-layer --oci-export particle-os/gaming/24.04 particle-os/gaming:latest +EOF +} + +# HARDWARE DETECTION & AUTO-CONFIGURATION: +# apt-layer --detect-hardware # Detect hardware and auto-configure +# apt-layer --show-hardware-info # Show detailed hardware information +# apt-layer --auto-configure-modules # Auto-configure kernel modules +# apt-layer --install-enabled-modules # Install all enabled modules + +# KERNEL PATCHING (Ubuntu-specific): +# apt-layer --list-patches # List available kernel patches +# apt-layer --list-enabled-patches # List enabled kernel patches +# apt-layer --enable-patch patch-name # Enable specific kernel patch +# apt-layer --disable-patch patch-name # Disable specific kernel patch +# apt-layer --apply-patch [patch-name] # Apply specific or all enabled patches +# apt-layer --update-kernel-args # Update kernel arguments for patches + +# Initialize Particle-OS system +initialize_particle_system() { + log_info "Initializing Particle-OS system..." "apt-layer" + + # Check if running as root + check_root + + # Create configuration file + if [[ ! -f "/usr/local/etc/particle-config.sh" ]]; then + log_info "Creating configuration file..." "apt-layer" + mkdir -p "/usr/local/etc" + + cat > "/usr/local/etc/particle-config.sh" << 'EOF' +#!/bin/bash +# Particle-OS Configuration File +# This file contains the main configuration for Particle-OS + +# Workspace and directory configuration +PARTICLE_WORKSPACE="/var/lib/particle-os" +PARTICLE_CONFIG_DIR="/usr/local/etc/particle-os" +PARTICLE_LOG_DIR="/var/log/particle-os" +PARTICLE_CACHE_DIR="/var/cache/particle-os" + +# Build and temporary directories +PARTICLE_BUILD_DIR="$PARTICLE_WORKSPACE/build" +PARTICLE_TEMP_DIR="$PARTICLE_WORKSPACE/temp" +PARTICLE_LAYERS_DIR="$PARTICLE_WORKSPACE/layers" + +# ComposeFS configuration +PARTICLE_COMPOSEFS_DIR="$PARTICLE_WORKSPACE/composefs" +PARTICLE_COMPOSEFS_SCRIPT="/usr/local/bin/composefs-alternative.sh" + +# Container configuration +PARTICLE_CONTAINER_RUNTIME="podman" +PARTICLE_CONTAINER_WORKSPACE="$PARTICLE_WORKSPACE/containers" + +# Live overlay configuration +PARTICLE_LIVE_OVERLAY_DIR="$PARTICLE_WORKSPACE/live-overlay" + +# Transaction configuration +PARTICLE_TRANSACTION_STATE="$PARTICLE_WORKSPACE/transaction-state" +PARTICLE_TRANSACTION_LOG="$PARTICLE_LOG_DIR/transaction.log" + +# Logging configuration +PARTICLE_LOG_LEVEL="info" +PARTICLE_LOG_COLOR="true" + +# Security configuration +PARTICLE_SECURITY_ENABLED="true" +PARTICLE_SECURITY_SCAN_LEVEL="standard" + +# Audit configuration +PARTICLE_AUDIT_ENABLED="true" +PARTICLE_AUDIT_RETENTION_DAYS="90" + +# OCI configuration +PARTICLE_OCI_ENABLED="true" +PARTICLE_OCI_WORKSPACE="$PARTICLE_WORKSPACE/oci" + +# Export variables for use in scripts +export PARTICLE_WORKSPACE +export PARTICLE_CONFIG_DIR +export PARTICLE_LOG_DIR +export PARTICLE_CACHE_DIR +export PARTICLE_BUILD_DIR +export PARTICLE_TEMP_DIR +export PARTICLE_LAYERS_DIR +export PARTICLE_COMPOSEFS_DIR +export PARTICLE_COMPOSEFS_SCRIPT +export PARTICLE_CONTAINER_RUNTIME +export PARTICLE_CONTAINER_WORKSPACE +export PARTICLE_LIVE_OVERLAY_DIR +export PARTICLE_TRANSACTION_STATE +export PARTICLE_TRANSACTION_LOG +export PARTICLE_LOG_LEVEL +export PARTICLE_LOG_COLOR +export PARTICLE_SECURITY_ENABLED +export PARTICLE_SECURITY_SCAN_LEVEL +export PARTICLE_AUDIT_ENABLED +export PARTICLE_AUDIT_RETENTION_DAYS +export PARTICLE_OCI_ENABLED +export PARTICLE_OCI_WORKSPACE +EOF + + chmod 644 "/usr/local/etc/particle-config.sh" + log_success "Configuration file created: /usr/local/etc/particle-config.sh" "apt-layer" + fi + + # Create workspace directory + if [[ ! -d "$WORKSPACE" ]]; then + log_info "Creating workspace directory..." "apt-layer" + mkdir -p "$WORKSPACE" + log_success "Workspace directory created: $WORKSPACE" "apt-layer" + fi + + # Create log directory + if [[ ! -d "/var/log/particle-os" ]]; then + log_info "Creating log directory..." "apt-layer" + mkdir -p "/var/log/particle-os" + log_success "Log directory created: /var/log/particle-os" "apt-layer" + fi + + # Create cache directory + if [[ ! -d "/var/cache/particle-os" ]]; then + log_info "Creating cache directory..." "apt-layer" + mkdir -p "/var/cache/particle-os" + log_success "Cache directory created: /var/cache/particle-os" "apt-layer" + fi + + # Initialize workspace subdirectories + init_workspace + + log_success "Particle-OS system initialization completed successfully!" "apt-layer" + echo "" + echo "System is now ready for use. You can run:" + echo " apt-layer --help" + echo " apt-layer status" + echo " apt-layer --list" +} + +# Main execution +main() { + # Initialize deployment database + init_deployment_db + + # Check for incomplete transactions first + check_incomplete_transactions + + # Check if system needs initialization (skip for help and initialization commands) + if [[ "${1:-}" != "--init" && "${1:-}" != "--reinit" && "${1:-}" != "--rm-init" && "${1:-}" != "--reset" && "${1:-}" != "--status" && "${1:-}" != "--help" && "${1:-}" != "-h" && "${1:-}" != "--help-full" && "${1:-}" != "--examples" && "${1:-}" != "--version" ]]; then + check_initialization_needed + fi + + # Parse command line arguments first (before dependency checks) + case "${1:-}" in + --init) + # Initialize apt-layer system + initialize_apt_layer_system + exit 0 + ;; + --reinit) + # Reinitialize apt-layer system (force recreation) + if command -v reinitialize_apt_layer_system >/dev/null 2>&1; then + reinitialize_apt_layer_system + else + log_error "Reinit function not available" "apt-layer" + exit 1 + fi + exit 0 + ;; + --rm-init) + # Remove apt-layer system (cleanup) + if command -v remove_apt_layer_system >/dev/null 2>&1; then + remove_apt_layer_system + else + log_error "Remove init function not available" "apt-layer" + exit 1 + fi + exit 0 + ;; + --status) + # Show apt-layer system status + if command -v show_apt_layer_system_status >/dev/null 2>&1; then + show_apt_layer_system_status + else + log_error "Status function not available" "apt-layer" + exit 1 + fi + exit 0 + ;; + --reset) + # Reset apt-layer system + reset_apt_layer_system + exit 0 + ;; + --help|-h) + show_usage + exit 0 + ;; + --help-full) + show_full_usage + exit 0 + ;; + --examples) + show_examples + exit 0 + ;; + --version) + show_version + exit 0 + ;; + layer) + if [[ "${2:-}" == "--help" || "${2:-}" == "-h" ]]; then + show_layer_help + exit 0 + fi + ;; + live) + if [[ "${2:-}" == "--help" || "${2:-}" == "-h" ]]; then + show_live_help + exit 0 + fi + ;; + rpm-ostree) + if [[ "${2:-}" == "--help" || "${2:-}" == "-h" ]]; then + show_rpm_ostree_help + exit 0 + fi + ;; + image) + if [[ "${2:-}" == "--help" || "${2:-}" == "-h" ]]; then + show_image_help + exit 0 + fi + ;; + security) + if [[ "${2:-}" == "--help" || "${2:-}" == "-h" ]]; then + show_security_help + exit 0 + fi + ;; + audit) + if [[ "${2:-}" == "--help" || "${2:-}" == "-h" ]]; then + show_audit_help + exit 0 + fi + ;; + admin) + if [[ "${2:-}" == "--help" || "${2:-}" == "-h" ]]; then + show_admin_help + exit 0 + fi + ;; + enterprise) + if [[ "${2:-}" == "--help" || "${2:-}" == "-h" ]]; then + show_enterprise_help + exit 0 + fi + ;; + cloud) + if [[ "${2:-}" == "--help" || "${2:-}" == "-h" ]]; then + show_cloud_help + exit 0 + fi + ;; + kubernetes) + if [[ "${2:-}" == "--help" || "${2:-}" == "-h" ]]; then + show_cloud_help + exit 0 + fi + ;; + --list) + list_branches + exit 0 + ;; + --info) + if [ -z "${2:-}" ]; then + log_error "Image name required for --info" "apt-layer" + show_usage + exit 1 + fi + show_branch_info "$2" + exit 0 + ;; + --remove) + if [ -z "${2:-}" ]; then + log_error "Image name required for --remove" "apt-layer" + show_usage + exit 1 + fi + remove_image "$2" + exit 0 + ;; + --oci-status) + # Show OCI integration system status + oci_status + exit 0 + ;; + --live-overlay) + # Live overlay management + require_root "live overlay management" + if [ -z "${2:-}" ]; then + log_error "Action required for --live-overlay" "apt-layer" + show_usage + exit 1 + fi + + local action="$2" + shift 2 + local options=("$@") + + manage_live_overlay "$action" "${options[@]}" + ;; + --live-install) + # Live system installation + require_root "live system installation" + if [ $# -lt 2 ]; then + log_error "No packages specified for --live-install" "apt-layer" + show_usage + exit 1 + fi + + shift + local packages=("$@") + + live_install "${packages[@]}" + ;; + --live-dpkg) + # Live system dpkg installation (offline/overlay optimized) + require_root "live system dpkg installation" + if [ $# -lt 2 ]; then + log_error "No .deb files specified for --live-dpkg" "apt-layer" + show_usage + exit 1 + fi + + shift + local deb_files=("$@") + + live_dpkg_install "${deb_files[@]}" + ;; + --live-commit) + # Commit live overlay changes + require_root "live overlay commit" + local message="${2:-Live overlay changes}" + commit_live_overlay "$message" + ;; + --live-rollback) + # Rollback live overlay changes + require_root "live overlay rollback" + rollback_live_overlay + ;; + orchestration) + # Container orchestration + if [ -z "${2:-}" ]; then + log_error "Action required for orchestration" "apt-layer" + show_usage + exit 1 + fi + local action="$2" + shift 2 + local args=("$@") + + handle_orchestration_command "$action" "${args[@]}" + exit 0 + ;; + multicloud) + # Multi-cloud deployment + if [ -z "${2:-}" ]; then + log_error "Action required for multicloud" "apt-layer" + show_usage + exit 1 + fi + local action="$2" + shift 2 + local args=("$@") + + handle_multicloud_command "$action" "${args[@]}" + exit 0 + ;; + cloud-security) + # Cloud-native security + if [ -z "${2:-}" ]; then + log_error "Action required for cloud-security" "apt-layer" + show_usage + exit 1 + fi + local action="$2" + shift 2 + local args=("$@") + + handle_cloud_security_command "$action" "${args[@]}" + exit 0 + ;; + ostree) + # OSTree atomic package management interface + local subcommand="${2:-}" + case "$subcommand" in + compose) + local compose_action="${3:-}" + shift 3 + case "$compose_action" in + install) + ostree_compose_install "$@" + ;; + remove) + ostree_compose_remove "$@" + ;; + update) + ostree_compose_update "$@" + ;; + *) + log_error "Invalid compose action: $compose_action" "apt-layer" + log_info "Valid actions: install, remove, update" "apt-layer" + show_usage + exit 1 + ;; + esac + ;; + log) + shift 2 + ostree_log "$@" + ;; + diff) + shift 2 + ostree_diff "$@" + ;; + rollback) + shift 2 + ostree_rollback "$@" + ;; + status) + shift 2 + ostree_status "$@" + ;; + cleanup) + shift 2 + ostree_cleanup "$@" + ;; + *) + log_error "Invalid ostree subcommand: $subcommand" "apt-layer" + log_info "Valid subcommands: compose, log, diff, rollback, status, cleanup" "apt-layer" + show_usage + exit 1 + ;; + esac + ;; + *) + # Check for empty argument + if [ -z "${1:-}" ]; then + log_error "No command specified" "apt-layer" + show_usage + exit 1 + fi + + # Regular layer creation (legacy mode) + if [ $# -lt 2 ]; then + log_error "Insufficient arguments for layer creation" "apt-layer" + show_usage + exit 1 + fi + + local base_image="$1" + local new_image="$2" + shift 2 + local packages=("$@") + + create_layer "$base_image" "$new_image" "${packages[@]}" + ;; + esac +} + +# Run main function +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi + +# --- END OF SCRIPTLET: 99-main.sh --- + +# ============================================================================ +# Embedded Configuration Files +# ============================================================================ + +# Enterprise-grade JSON configuration system +# All configuration files are embedded for self-contained operation +# Configuration can be overridden via command-line arguments + +# Configuration files to be embedded: +# - apt-layer-settings.json +# - audit-settings.json +# - backup-policy.json +# - kernel-modules.json +# - kernel-patches.json +# - maintenance.json +# - oci-settings.json +# - package-management.json +# - paths.json +# - security-policy.json +# - signing-policy.json +# - users.json + +# Embedded configuration: apt-layer-settings +# File size: 307 +APT_LAYER_SETTINGS_CONFIG=$(cat << 'EOF' +{ + "default_container_runtime": "podman", + "default_workspace": "/var/lib/particle-os", + "feature_toggles": { + "live_overlay": true, + "oci_integration": true, + "security_scanning": true, + "audit_reporting": true, + "layer_signing": true + }, + "log_level": "info", + "color_output": true +} +EOF +) + +# Embedded configuration: audit-settings +# File size: 166 +AUDIT_SETTINGS_CONFIG=$(cat << 'EOF' +{ + "log_retention_days": 90, + "remote_log_endpoint": "https://audit.example.com/submit", + "compliance_frameworks": [ + "SOX", + "PCI-DSS" + ], + "log_verbosity": "info" +} +EOF +) + +# Embedded configuration: backup-policy +# File size: 158 +BACKUP_POLICY_CONFIG=$(cat << 'EOF' +{ + "backup_frequency": "weekly", + "retention_days": 60, + "compression": true, + "encryption": false, + "backup_location": "/var/lib/particle-os/backups" +} +EOF +) + +# Embedded configuration: kernel-modules +# File size: 6.1K +KERNEL_MODULES_CONFIG=$(cat << 'EOF' +{ + "kernel_modules": { + "common": { + "description": "Common kernel modules for general hardware support", + "modules": { + "v4l2loopback": { + "description": "Virtual video devices for screen recording and streaming", + "package": "v4l2loopback-dkms", + "kernel_args": [], + "enabled": true + }, + "gpd-fan-kmod": { + "description": "GPD Win Max fan control and thermal management", + "package": "gpd-fan-kmod", + "kernel_args": [], + "enabled": false + }, + "nct6687d": { + "description": "Nuvoton NCT6687-R support for AMD B550 chipset motherboards", + "package": "nct6687d-dkms", + "kernel_args": [], + "enabled": false + }, + "ryzen-smu": { + "description": "AMD Ryzen SMU (System Management Unit) access", + "package": "ryzen-smu-dkms", + "kernel_args": [], + "enabled": false + }, + "system76": { + "description": "System76 laptop drivers and hardware support", + "package": "system76-dkms", + "kernel_args": [], + "enabled": false + }, + "zenergy": { + "description": "AMD energy monitoring with jiffies for non-root access", + "package": "zenergy-dkms", + "kernel_args": [], + "enabled": false + } + } + }, + "nvidia": { + "description": "NVIDIA GPU driver support", + "modules": { + "nvidia": { + "description": "NVIDIA closed proprietary drivers for legacy hardware", + "package": "nvidia-driver-535", + "kernel_args": [ + "nvidia-drm.modeset=1" + ], + "enabled": false, + "hardware_support": { + "geforce_rtx": [ + "40", + "30", + "20" + ], + "geforce": [ + "16", + "10", + "900", + "700" + ], + "quadro": [ + "T4", + "T4G", + "P2000", + "P4000", + "P5000", + "P6000", + "K2200", + "M2000", + "M4000", + "M5000", + "M6000" + ], + "tesla": [ + "T4", + "T4G", + "V100", + "P100", + "P40", + "P4", + "M60", + "M40", + "M6", + "M4" + ] + } + }, + "nvidia-open": { + "description": "NVIDIA open source drivers for latest hardware", + "package": "nvidia-driver-open-535", + "kernel_args": [ + "nvidia-drm.modeset=1" + ], + "enabled": false, + "hardware_support": { + "geforce_rtx": [ + "50", + "40", + "30", + "20" + ], + "geforce": [ + "16" + ] + } + } + } + }, + "gaming": { + "description": "Gaming-specific kernel modules and optimizations", + "modules": { + "steam-deck": { + "description": "Steam Deck specific optimizations and patches", + "package": "steam-deck-dkms", + "kernel_args": [ + "steam_deck.fan_control=1" + ], + "enabled": false + }, + "gaming-peripherals": { + "description": "Gaming mouse, keyboard, and controller support", + "package": "gaming-peripherals-dkms", + "kernel_args": [], + "enabled": false + } + } + }, + "virtualization": { + "description": "Virtualization and container support", + "modules": { + "virtualbox": { + "description": "VirtualBox virtualization support", + "package": "virtualbox-dkms", + "kernel_args": [], + "enabled": false + }, + "vmware": { + "description": "VMware virtualization support", + "package": "open-vm-tools-dkms", + "kernel_args": [], + "enabled": false + }, + "docker": { + "description": "Docker container support", + "package": "docker-dkms", + "kernel_args": [], + "enabled": false + } + } + }, + "storage": { + "description": "Advanced storage and filesystem support", + "modules": { + "zfs": { + "description": "OpenZFS advanced file system and volume manager", + "package": "zfs-dkms", + "kernel_args": [], + "enabled": false + }, + "btrfs": { + "description": "Btrfs filesystem support", + "package": "btrfs-dkms", + "kernel_args": [], + "enabled": false + } + } + }, + "network": { + "description": "Network adapter and protocol support", + "modules": { + "intel-nic": { + "description": "Intel network interface card support", + "package": "intel-nic-dkms", + "kernel_args": [], + "enabled": false + }, + "broadcom-nic": { + "description": "Broadcom network interface card support", + "package": "broadcom-nic-dkms", + "kernel_args": [], + "enabled": false + } + } + } + }, + "kernel_variants": { + "ubuntu-generic": { + "description": "Ubuntu generic kernel", + "headers_package": "linux-headers-generic", + "image_package": "linux-image-generic", + "enabled": true + }, + "ubuntu-generic-hwe": { + "description": "Ubuntu generic HWE kernel", + "headers_package": "linux-headers-generic-hwe-24.04", + "image_package": "linux-image-generic-hwe-24.04", + "enabled": true + }, + "ubuntu-lowlatency": { + "description": "Ubuntu low latency kernel", + "headers_package": "linux-headers-lowlatency", + "image_package": "linux-image-lowlatency", + "enabled": false + }, + "ubuntu-lowlatency-hwe": { + "description": "Ubuntu low latency HWE kernel", + "headers_package": "linux-headers-lowlatency-hwe-24.04", + "image_package": "linux-image-lowlatency-hwe-24.04", + "enabled": false + } + }, + "hardware_detection": { + "auto_detect": true, + "detection_scripts": { + "gpu": "/usr/local/bin/detect-gpu.sh", + "cpu": "/usr/local/bin/detect-cpu.sh", + "motherboard": "/usr/local/bin/detect-motherboard.sh", + "storage": "/usr/local/bin/detect-storage.sh", + "network": "/usr/local/bin/detect-network.sh" + } + }, + "build_configuration": { + "containerized_builds": true, + "build_timeout": 3600, + "parallel_builds": 2, + "cache_built_modules": true, + "cache_directory": "/var/cache/particle-os/dkms", + "build_logs_directory": "/var/log/particle-os/dkms" + } +} +EOF +) + +# Embedded configuration: kernel-patches +# File size: 15K +KERNEL_PATCHES_CONFIG=$(cat << 'EOF' +{ + "kernel_patches": { + "gaming": { + "description": "Gaming performance and compatibility patches", + "enabled": true, + "patches": { + "steam-deck": { + "description": "Steam Deck specific optimizations for Ubuntu", + "url": "https://github.com/ValveSoftware/linux-kernel/raw/steamdeck-6.1.y/patches/0001-steam-deck-optimizations.patch", + "enabled": false, + "hardware_requirements": [ + "steam_deck" + ], + "kernel_args": [ + "steam_deck.fan_control=1", + "steam_deck.performance_mode=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Valve's official Steam Deck kernel patches" + }, + "handheld": { + "description": "Handheld device optimizations for Ubuntu", + "url": "https://github.com/linux-surface/linux-surface/raw/master/patches/5.15/0001-handheld-optimizations.patch", + "enabled": false, + "hardware_requirements": [ + "handheld_device" + ], + "kernel_args": [ + "handheld.fan_control=1", + "handheld.performance_mode=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1" + ], + "note": "Linux Surface project patches for handheld devices" + }, + "gaming-performance": { + "description": "General gaming performance optimizations for Ubuntu", + "url": "https://github.com/graysky2/kernel_gcc_patch/raw/master/enable_additional_cpu_optimizations_for_gcc_v12.1%2B_kernel_v5.15%2B.patch", + "enabled": true, + "kernel_args": [ + "gaming.performance_mode=1", + "gaming.low_latency=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Graysky2's CPU optimization patches for gaming" + }, + "wine-compatibility": { + "description": "Wine and Proton compatibility improvements for Ubuntu", + "url": "https://github.com/Frogging-Family/linux-tkg/raw/master/patches/0001-wine-compatibility.patch", + "enabled": true, + "kernel_args": [ + "wine.compatibility_mode=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Frogging-Family's Wine compatibility patches" + } + } + }, + "hardware": { + "description": "Hardware-specific support patches for Ubuntu", + "enabled": true, + "patches": { + "amd-optimizations": { + "description": "AMD CPU and GPU optimizations for Ubuntu", + "url": "https://github.com/graysky2/kernel_gcc_patch/raw/master/enable_additional_cpu_optimizations_for_gcc_v12.1%2B_kernel_v5.15%2B.patch", + "enabled": false, + "hardware_requirements": [ + "amd_cpu", + "amd_gpu" + ], + "kernel_args": [ + "amd.performance_mode=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Graysky2's AMD optimization patches" + }, + "intel-optimizations": { + "description": "Intel CPU and GPU optimizations for Ubuntu", + "url": "https://github.com/graysky2/kernel_gcc_patch/raw/master/enable_additional_cpu_optimizations_for_gcc_v12.1%2B_kernel_v5.15%2B.patch", + "enabled": false, + "hardware_requirements": [ + "intel_cpu", + "intel_gpu" + ], + "kernel_args": [ + "intel.performance_mode=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Graysky2's Intel optimization patches" + }, + "nvidia-optimizations": { + "description": "NVIDIA GPU optimizations for Ubuntu", + "url": "https://github.com/Frogging-Family/linux-tkg/raw/master/patches/0001-nvidia-optimizations.patch", + "enabled": false, + "hardware_requirements": [ + "nvidia_gpu" + ], + "kernel_args": [ + "nvidia.performance_mode=1", + "nvidia.drm.modeset=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Frogging-Family's NVIDIA optimization patches" + }, + "system76": { + "description": "System76 hardware support for Ubuntu", + "url": "https://github.com/system76/linux/raw/master/patches/0001-system76-ubuntu.patch", + "enabled": false, + "hardware_requirements": [ + "system76_hardware" + ], + "kernel_args": [ + "system76.fan_control=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "System76's official Ubuntu kernel patches" + } + } + }, + "performance": { + "description": "General performance optimizations for Ubuntu", + "enabled": true, + "patches": { + "cpu-scheduler": { + "description": "CPU scheduler optimizations for Ubuntu", + "url": "https://github.com/Frogging-Family/linux-tkg/raw/master/patches/0001-cpu-scheduler-optimizations.patch", + "enabled": true, + "kernel_args": [ + "sched.performance_mode=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Frogging-Family's CPU scheduler patches" + }, + "memory-management": { + "description": "Memory management optimizations for Ubuntu", + "url": "https://github.com/Frogging-Family/linux-tkg/raw/master/patches/0001-memory-management-optimizations.patch", + "enabled": true, + "kernel_args": [ + "vm.performance_mode=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Frogging-Family's memory management patches" + }, + "io-scheduler": { + "description": "I/O scheduler optimizations for Ubuntu", + "url": "https://github.com/Frogging-Family/linux-tkg/raw/master/patches/0001-io-scheduler-optimizations.patch", + "enabled": true, + "kernel_args": [ + "elevator=bfq" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Frogging-Family's I/O scheduler patches" + }, + "network-optimizations": { + "description": "Network performance optimizations for Ubuntu", + "url": "https://github.com/Frogging-Family/linux-tkg/raw/master/patches/0001-network-optimizations.patch", + "enabled": true, + "kernel_args": [ + "net.core.rmem_max=16777216", + "net.core.wmem_max=16777216" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Frogging-Family's network optimization patches" + } + } + }, + "security": { + "description": "Security and hardening patches for Ubuntu", + "enabled": true, + "patches": { + "security-hardening": { + "description": "General security hardening for Ubuntu", + "url": "https://github.com/Ubuntu/linux/raw/master/security/0001-security-hardening.patch", + "enabled": true, + "kernel_args": [ + "security.hardening=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Ubuntu's official security hardening patches" + }, + "spectre-meltdown": { + "description": "Spectre and Meltdown mitigations for Ubuntu", + "url": "https://github.com/Ubuntu/linux/raw/master/security/0001-spectre-meltdown-mitigations.patch", + "enabled": true, + "kernel_args": [ + "spectre_v2=on", + "meltdown=on" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Ubuntu's official Spectre/Meltdown mitigation patches" + } + } + }, + "compatibility": { + "description": "Software compatibility patches for Ubuntu", + "enabled": true, + "patches": { + "wine": { + "description": "Wine compatibility improvements for Ubuntu", + "url": "https://github.com/Frogging-Family/linux-tkg/raw/master/patches/0001-wine-compatibility.patch", + "enabled": true, + "kernel_args": [ + "wine.compatibility=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Frogging-Family's Wine compatibility patches" + }, + "proton": { + "description": "Proton compatibility improvements for Ubuntu", + "url": "https://github.com/Frogging-Family/linux-tkg/raw/master/patches/0001-proton-compatibility.patch", + "enabled": true, + "kernel_args": [ + "proton.compatibility=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Frogging-Family's Proton compatibility patches" + }, + "virtualization": { + "description": "Virtualization compatibility for Ubuntu", + "url": "https://github.com/Ubuntu/linux/raw/master/virtualization/0001-virtualization-compatibility.patch", + "enabled": true, + "kernel_args": [ + "virtualization.compatibility=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Ubuntu's official virtualization compatibility patches" + } + } + }, + "ubuntu_specific": { + "description": "Ubuntu-specific patches and optimizations", + "enabled": true, + "patches": { + "ubuntu-gaming": { + "description": "Ubuntu gaming optimizations", + "url": "https://github.com/Ubuntu/linux/raw/master/gaming/0001-ubuntu-gaming-optimizations.patch", + "enabled": true, + "kernel_args": [ + "ubuntu.gaming_mode=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Ubuntu's official gaming optimization patches" + }, + "ubuntu-performance": { + "description": "Ubuntu performance optimizations", + "url": "https://github.com/Ubuntu/linux/raw/master/performance/0001-ubuntu-performance-optimizations.patch", + "enabled": true, + "kernel_args": [ + "ubuntu.performance_mode=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Ubuntu's official performance optimization patches" + }, + "ubuntu-desktop": { + "description": "Ubuntu desktop optimizations", + "url": "https://github.com/Ubuntu/linux/raw/master/desktop/0001-ubuntu-desktop-optimizations.patch", + "enabled": true, + "kernel_args": [ + "ubuntu.desktop_mode=1" + ], + "ubuntu_compatible": true, + "kernel_versions": [ + "5.15", + "6.1", + "6.2" + ], + "note": "Ubuntu's official desktop optimization patches" + } + } + } + }, + "patch_application": { + "auto_apply": true, + "backup_patches": true, + "patch_directory": "/var/lib/particle-os/kernel-patches", + "backup_directory": "/var/lib/particle-os/kernel-patches/backup", + "log_directory": "/var/log/particle-os/kernel-patches", + "ubuntu_specific": { + "use_dkms": true, + "use_dkpg": true, + "patch_format": "diff", + "apply_method": "patch -p1" + } + }, + "kernel_variants": { + "ubuntu-generic": { + "description": "Ubuntu generic kernel with patches", + "base_package": "linux-generic", + "headers_package": "linux-headers-generic", + "patches_enabled": [ + "gaming", + "performance", + "security", + "compatibility", + "ubuntu_specific" + ], + "enabled": true, + "ubuntu_version": "24.04" + }, + "ubuntu-generic-hwe": { + "description": "Ubuntu generic HWE kernel with patches", + "base_package": "linux-generic-hwe-24.04", + "headers_package": "linux-headers-generic-hwe-24.04", + "patches_enabled": [ + "gaming", + "performance", + "security", + "compatibility", + "ubuntu_specific" + ], + "enabled": true, + "ubuntu_version": "24.04" + }, + "ubuntu-lowlatency": { + "description": "Ubuntu low latency kernel with patches", + "base_package": "linux-lowlatency", + "headers_package": "linux-headers-lowlatency", + "patches_enabled": [ + "gaming", + "performance", + "security", + "compatibility", + "ubuntu_specific" + ], + "enabled": false, + "ubuntu_version": "24.04" + }, + "ubuntu-lowlatency-hwe": { + "description": "Ubuntu low latency HWE kernel with patches", + "base_package": "linux-lowlatency-hwe-24.04", + "headers_package": "linux-headers-lowlatency-hwe-24.04", + "patches_enabled": [ + "gaming", + "performance", + "security", + "compatibility", + "ubuntu_specific" + ], + "enabled": false, + "ubuntu_version": "24.04" + } + }, + "hardware_detection": { + "auto_detect_patches": true, + "detection_scripts": { + "steam_deck": "/usr/local/bin/detect-steam-deck.sh", + "handheld_device": "/usr/local/bin/detect-handheld.sh", + "amd_cpu": "/usr/local/bin/detect-amd-cpu.sh", + "amd_gpu": "/usr/local/bin/detect-amd-gpu.sh", + "intel_cpu": "/usr/local/bin/detect-intel-cpu.sh", + "intel_gpu": "/usr/local/bin/detect-intel-gpu.sh", + "nvidia_gpu": "/usr/local/bin/detect-nvidia-gpu.sh", + "system76_hardware": "/usr/local/bin/detect-system76.sh" + } + }, + "ubuntu_integration": { + "use_ubuntu_repositories": true, + "ubuntu_kernel_sources": "https://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/", + "ubuntu_patch_workflow": "https://wiki.ubuntu.com/Kernel/Dev/KernelMaintenance", + "ubuntu_kernel_team": "https://launchpad.net/~ubuntu-kernel", + "ubuntu_kernel_ppa": "ppa:ubuntu-kernel-team/ppa" + }, + "patch_notes": { + "important": "These patch URLs are examples and may not all exist. In a real implementation, you would need to verify each patch URL and ensure it's compatible with your specific Ubuntu kernel version.", + "recommendations": [ + "Use Ubuntu's official kernel patches when available", + "Verify patch compatibility before applying", + "Test patches in a safe environment first", + "Keep backups of kernel configuration", + "Use DKMS for kernel module patches when possible" + ], + "real_sources": [ + "https://github.com/graysky2/kernel_gcc_patch - Real CPU optimization patches", + "https://github.com/Frogging-Family/linux-tkg - Real gaming kernel patches", + "https://github.com/ValveSoftware/linux-kernel - Real Steam Deck patches", + "https://github.com/linux-surface/linux-surface - Real Surface/handheld patches", + "https://github.com/system76/linux - Real System76 patches" + ] + } +} +EOF +) + +# Embedded configuration: maintenance +# File size: 189 +MAINTENANCE_CONFIG=$(cat << 'EOF' +{ + "retention_days": 30, + "keep_recent": 2, + "deployments_dir": "/var/lib/particle-os/deployments", + "logs_dir": "/var/log/apt-layer", + "backups_dir": "/var/lib/particle-os/backups" +} +EOF +) + +# Embedded configuration: oci-settings +# File size: 193 +OCI_SETTINGS_CONFIG=$(cat << 'EOF' +{ + "registry_url": "docker.io/particleos", + "allowed_base_images": [ + "ubuntu:24.04", + "debian:12" + ], + "authentication": { + "username": "particlebot", + "password": "examplepassword" + } +} +EOF +) + +# Embedded configuration: package-management +# File size: 180 +PACKAGE_MANAGEMENT_CONFIG=$(cat << 'EOF' +{ + "allowed_repositories": [ + "main", + "universe", + "multiverse" + ], + "dependency_resolution": "strict", + "package_pinning": { + "firefox": "125.0.1", + "steam": "1.0.0.75" + } +} +EOF +) + +# Embedded configuration: paths +# File size: 4.6K +PATHS_CONFIG=$(cat << 'EOF' +{ + "apt_layer_paths": { + "description": "apt-layer system path configuration", + "version": "1.0", + "main_directories": { + "workspace": { + "path": "/var/lib/apt-layer", + "description": "Main apt-layer workspace directory", + "permissions": "755", + "owner": "root:root" + }, + "logs": { + "path": "/var/log/apt-layer", + "description": "apt-layer log files directory", + "permissions": "755", + "owner": "root:root" + }, + "cache": { + "path": "/var/cache/apt-layer", + "description": "apt-layer cache directory", + "permissions": "755", + "owner": "root:root" + } + }, + "workspace_subdirectories": { + "build": { + "path": "/var/lib/apt-layer/build", + "description": "Build artifacts and temporary files", + "permissions": "755", + "owner": "root:root" + }, + "live_overlay": { + "path": "/var/lib/apt-layer/live-overlay", + "description": "Live overlay system for temporary changes", + "permissions": "755", + "owner": "root:root" + }, + "composefs": { + "path": "/var/lib/apt-layer/composefs", + "description": "ComposeFS layers and metadata", + "permissions": "755", + "owner": "root:root" + }, + "ostree_commits": { + "path": "/var/lib/apt-layer/ostree-commits", + "description": "OSTree commit history and metadata", + "permissions": "755", + "owner": "root:root" + }, + "deployments": { + "path": "/var/lib/apt-layer/deployments", + "description": "Deployment directories and state", + "permissions": "755", + "owner": "root:root" + }, + "history": { + "path": "/var/lib/apt-layer/history", + "description": "Deployment history and rollback data", + "permissions": "755", + "owner": "root:root" + }, + "bootloader": { + "path": "/var/lib/apt-layer/bootloader", + "description": "Bootloader state and configuration", + "permissions": "755", + "owner": "root:root" + }, + "transaction_state": { + "path": "/var/lib/apt-layer/transaction-state", + "description": "Transaction state and temporary data", + "permissions": "755", + "owner": "root:root" + } + }, + "files": { + "deployment_db": { + "path": "/var/lib/apt-layer/deployments.json", + "description": "Deployment database file", + "permissions": "644", + "owner": "root:root" + }, + "current_deployment": { + "path": "/var/lib/apt-layer/current-deployment", + "description": "Current deployment identifier file", + "permissions": "644", + "owner": "root:root" + }, + "pending_deployment": { + "path": "/var/lib/apt-layer/pending-deployment", + "description": "Pending deployment identifier file", + "permissions": "644", + "owner": "root:root" + }, + "transaction_log": { + "path": "/var/lib/apt-layer/transaction.log", + "description": "Transaction log file", + "permissions": "644", + "owner": "root:root" + } + }, + "fallback_paths": { + "description": "Fallback paths for different environments", + "wsl": { + "workspace": "/mnt/wsl/apt-layer", + "logs": "/mnt/wsl/apt-layer/logs", + "cache": "/mnt/wsl/apt-layer/cache" + }, + "container": { + "workspace": "/tmp/apt-layer", + "logs": "/tmp/apt-layer/logs", + "cache": "/tmp/apt-layer/cache" + }, + "test": { + "workspace": "/tmp/apt-layer-test", + "logs": "/tmp/apt-layer-test/logs", + "cache": "/tmp/apt-layer-test/cache" + } + }, + "environment_variables": { + "description": "Environment variable mappings", + "APT_LAYER_WORKSPACE": "workspace", + "APT_LAYER_LOG_DIR": "logs", + "APT_LAYER_CACHE_DIR": "cache", + "BUILD_DIR": "workspace_subdirectories.build", + "LIVE_OVERLAY_DIR": "workspace_subdirectories.live_overlay", + "COMPOSEFS_DIR": "workspace_subdirectories.composefs", + "OSTREE_COMMITS_DIR": "workspace_subdirectories.ostree_commits", + "DEPLOYMENTS_DIR": "workspace_subdirectories.deployments", + "HISTORY_DIR": "workspace_subdirectories.history", + "BOOTLOADER_STATE_DIR": "workspace_subdirectories.bootloader", + "TRANSACTION_STATE": "workspace_subdirectories.transaction_state", + "DEPLOYMENT_DB": "files.deployment_db", + "CURRENT_DEPLOYMENT_FILE": "files.current_deployment", + "PENDING_DEPLOYMENT_FILE": "files.pending_deployment", + "TRANSACTION_LOG": "files.transaction_log" + } + } +} +EOF +) + +# Embedded configuration: security-policy +# File size: 197 +SECURITY_POLICY_CONFIG=$(cat << 'EOF' +{ + "require_gpg_signature": true, + "allowed_packages": [ + "firefox", + "steam", + "vscode" + ], + "blocked_packages": [ + "telnet", + "ftp" + ], + "vulnerability_threshold": "high", + "enforce_signature": true +} +EOF +) + +# Embedded configuration: signing-policy +# File size: 166 +SIGNING_POLICY_CONFIG=$(cat << 'EOF' +{ + "allowed_methods": [ + "gpg", + "sigstore" + ], + "trusted_keys": [ + "key1.gpg", + "key2.sigstore" + ], + "require_signature": true, + "revocation_list": [ + "revoked-key1.gpg" + ] +} +EOF +) + +# Embedded configuration: users +# File size: 264 +USERS_CONFIG=$(cat << 'EOF' +{ + "users": [ + { + "username": "admin", + "role": "admin", + "enabled": true + }, + { + "username": "john", + "role": "package_manager", + "enabled": true + }, + { + "username": "jane", + "role": "viewer", + "enabled": false + } + ], + "roles": [ + "admin", + "package_manager", + "viewer" + ] +} +EOF +) + +# ============================================================================ +# External Configuration Loading (Future Enhancement) +# ============================================================================ + +# Function to load configuration from external files +# Usage: load_config_from_file "config-name" +load_config_from_file() { + local config_name="$1" + local config_file="/etc/apt-layer/config/${config_name}.json" + if [[ -f "$config_file" ]]; then + jq -r '.' "$config_file" + else + log_error "Configuration file not found: $config_file" "apt-layer" + exit 1 + fi +} + +# ============================================================================ +# Main Execution +# ============================================================================ + +# Run main function if script is executed directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/artifacts/install-apt-layer.sh b/artifacts/install-apt-layer.sh new file mode 100644 index 0000000..50f42b7 --- /dev/null +++ b/artifacts/install-apt-layer.sh @@ -0,0 +1,475 @@ +#!/bin/bash + +# apt-layer Installation Script +# This script installs the apt-layer tool, its dependencies, creates necessary directories and files, +# and sets up the system to use apt-layer. If already installed, it will update the tool. +# +# Usage: +# ./install-apt-layer.sh # Install or update apt-layer +# ./install-apt-layer.sh --uninstall # Remove apt-layer and all its files +# ./install-apt-layer.sh --reinstall # Remove and reinstall (reset to default state) +# ./install-apt-layer.sh --help # Show this help message + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +# Function to print colored output +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +print_header() { + echo -e "${BLUE}================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}================================${NC}" +} + +# Function to show help +show_help() { + cat << 'EOF' +apt-layer Installation Script + +This script installs the apt-layer tool, its dependencies, creates necessary directories and files, +and sets up the system to use apt-layer. + +Usage: + ./install-apt-layer.sh # Install or update apt-layer + ./install-apt-layer.sh --uninstall # Remove apt-layer and all its files + ./install-apt-layer.sh --reinstall # Remove and reinstall (reset to default state) + ./install-apt-layer.sh --help # Show this help message + +What this script does: + - Downloads the latest apt-layer.sh from the repository + - Installs required dependencies (jq, dos2unix, etc.) + - Creates necessary directories (/var/lib/apt-layer, /var/log/apt-layer, etc.) + - Sets up configuration files + - Makes apt-layer executable and available system-wide + - Initializes the apt-layer system + +Dependencies: + - curl or wget (for downloading) + - jq (for JSON processing) + - dos2unix (for Windows line ending conversion) + - sudo (for system installation) + +EOF +} + +# Function to check if running as root +check_root() { + if [[ $EUID -eq 0 ]]; then + print_error "This script should not be run as root. Use sudo for specific commands." + exit 1 + fi +} + +# Function to check dependencies +check_dependencies() { + print_status "Checking dependencies..." + + local missing_deps=() + + # Check for curl or wget + if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then + missing_deps+=("curl or wget") + fi + + # Check for jq + if ! command -v jq >/dev/null 2>&1; then + missing_deps+=("jq") + fi + + # Check for dos2unix + if ! command -v dos2unix >/dev/null 2>&1; then + missing_deps+=("dos2unix") + fi + + if [[ ${#missing_deps[@]} -gt 0 ]]; then + print_error "Missing required dependencies: ${missing_deps[*]}" + print_status "Installing missing dependencies..." + + # Try to install dependencies + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update + sudo apt-get install -y "${missing_deps[@]}" + elif command -v dnf >/dev/null 2>&1; then + sudo dnf install -y "${missing_deps[@]}" + elif command -v yum >/dev/null 2>&1; then + sudo yum install -y "${missing_deps[@]}" + else + print_error "Could not automatically install dependencies. Please install manually:" + print_error " ${missing_deps[*]}" + exit 1 + fi + fi + + print_success "All dependencies satisfied" +} + +# Function to download apt-layer +download_apt_layer() { + print_status "Downloading apt-layer..." + + local download_url="https://git.raines.xyz/robojerk/particle-os-tools/raw/branch/main/apt-layer.sh" + local temp_file="/tmp/apt-layer.sh" + + # Download using curl or wget + if command -v curl >/dev/null 2>&1; then + if curl -L -o "$temp_file" "$download_url"; then + print_success "Downloaded apt-layer using curl" + else + print_error "Failed to download apt-layer using curl" + return 1 + fi + elif command -v wget >/dev/null 2>&1; then + if wget -O "$temp_file" "$download_url"; then + print_success "Downloaded apt-layer using wget" + else + print_error "Failed to download apt-layer using wget" + return 1 + fi + else + print_error "No download tool available (curl or wget)" + return 1 + fi + + # Verify the downloaded file + if [[ ! -f "$temp_file" ]] || [[ ! -s "$temp_file" ]]; then + print_error "Downloaded file is empty or missing" + return 1 + fi + + # Convert line endings if needed + if command -v dos2unix >/dev/null 2>&1; then + dos2unix "$temp_file" 2>/dev/null || true + fi + + # Make executable + chmod +x "$temp_file" + + print_success "apt-layer downloaded and prepared" +} + +# Function to install apt-layer +install_apt_layer() { + print_status "Installing apt-layer..." + + local temp_file="/tmp/apt-layer.sh" + local install_dir="/usr/local/bin" + local config_dir="/usr/local/etc/apt-layer" + + # Create installation directory if it doesn't exist + sudo mkdir -p "$install_dir" + + # Install apt-layer + sudo cp "$temp_file" "$install_dir/apt-layer" + sudo chmod +x "$install_dir/apt-layer" + + # Create configuration directory + sudo mkdir -p "$config_dir" + + # Create paths.json configuration + sudo tee "$config_dir/paths.json" >/dev/null </dev/null 2>&1 +} + +# Function to check if apt-layer is up to date +check_for_updates() { + print_status "Checking for updates..." + + if ! is_apt_layer_installed; then + return 0 # Not installed, so no update needed + fi + + # For now, we'll always download the latest version + # In the future, this could check version numbers or timestamps + return 1 # Update needed +} + +# Main installation function +main_install() { + print_header "apt-layer Installation" + + # Check if running as root + check_root + + # Check dependencies + check_dependencies + + # Check if already installed + if is_apt_layer_installed; then + print_status "apt-layer is already installed" + if check_for_updates; then + print_status "apt-layer is up to date" + return 0 + else + print_status "Updating apt-layer..." + fi + else + print_status "Installing apt-layer..." + fi + + # Download and install + if download_apt_layer && install_apt_layer; then + print_success "apt-layer installation completed successfully" + print_status "You can now use 'apt-layer --help' to see available commands" + return 0 + else + print_error "apt-layer installation failed" + return 1 + fi +} + +# Main function +main() { + case "${1:-}" in + --help|-h) + show_help + exit 0 + ;; + --uninstall) + print_header "apt-layer Uninstallation" + uninstall_apt_layer + exit 0 + ;; + --reinstall) + print_header "apt-layer Reinstallation" + reinstall_apt_layer + exit 0 + ;; + "") + main_install + exit $? + ;; + *) + print_error "Unknown option: $1" + show_help + exit 1 + ;; + esac +} + +# Run main function with all arguments +main "$@" diff --git a/compile-apt-layer-local.sh b/compile-apt-layer-local.sh new file mode 100644 index 0000000..6ba5f1e --- /dev/null +++ b/compile-apt-layer-local.sh @@ -0,0 +1,183 @@ +#!/bin/bash + +# Local apt-layer compilation script +# This script compiles apt-layer and the installer locally + +set -e # Exit on any error + +echo "==========================================" +echo "🔧 COMPILING APT-LAYER LOCALLY" +echo "==========================================" + +# Check if we're in the right directory +if [[ ! -f "src/apt-layer/compile.sh" ]]; then + echo "❌ Error: Please run this script from the tools directory" + exit 1 +fi + +# Make compile scripts executable +echo "📝 Making compile scripts executable..." +chmod +x src/apt-layer/compile.sh +chmod +x src/apt-layer/compile-installer.sh + +# Compile apt-layer +echo "🔨 Compiling apt-layer..." +cd src/apt-layer +./compile.sh -o ../../apt-layer.sh +cd ../.. + +# Compile installer +echo "🔨 Compiling installer with latest paths.json..." +cd src/apt-layer +./compile-installer.sh -o ../../install-apt-layer.sh +cd ../.. + +# Verify compilation +echo "✅ Verifying compiled scripts..." + +# Check apt-layer.sh +if [[ ! -f apt-layer.sh ]]; then + echo "❌ apt-layer compilation failed - apt-layer.sh not found" + exit 1 +fi + +if [[ ! -x apt-layer.sh ]]; then + echo "❌ apt-layer script is not executable" + exit 1 +fi + +# Test apt-layer.sh syntax +if bash -n apt-layer.sh; then + echo "✅ apt-layer.sh syntax validation passed" +else + echo "❌ apt-layer.sh syntax validation failed" + exit 1 +fi + +# Check apt-layer.sh file size +file_size=$(du -h apt-layer.sh | cut -f1) +echo "📦 apt-layer.sh size: $file_size" + +# Count apt-layer.sh lines +line_count=$(wc -l < apt-layer.sh) +echo "📄 apt-layer.sh lines: $line_count" + +# Check install-apt-layer.sh +if [[ ! -f install-apt-layer.sh ]]; then + echo "❌ Installer compilation failed - install-apt-layer.sh not found" + exit 1 +fi + +if [[ ! -x install-apt-layer.sh ]]; then + echo "❌ Installer script is not executable" + exit 1 +fi + +# Test install-apt-layer.sh syntax +if bash -n install-apt-layer.sh; then + echo "✅ install-apt-layer.sh syntax validation passed" +else + echo "❌ install-apt-layer.sh syntax validation failed" + exit 1 +fi + +# Check install-apt-layer.sh file size +installer_size=$(du -h install-apt-layer.sh | cut -f1) +echo "📦 install-apt-layer.sh size: $installer_size" + +# Count install-apt-layer.sh lines +installer_lines=$(wc -l < install-apt-layer.sh) +echo "📄 install-apt-layer.sh lines: $installer_lines" + +# Test basic functionality +echo "🧪 Testing basic functionality..." + +# Test apt-layer.sh help command +if ./apt-layer.sh --help > /dev/null 2>&1; then + echo "✅ apt-layer.sh help command works" +else + echo "❌ apt-layer.sh help command failed" + exit 1 +fi + +# Test apt-layer.sh version info +if ./apt-layer.sh --version > /dev/null 2>&1; then + echo "✅ apt-layer.sh version command works" +else + echo "⚠️ apt-layer.sh version command not available (this is optional)" +fi + +# Test install-apt-layer.sh help command +if ./install-apt-layer.sh --help > /dev/null 2>&1; then + echo "✅ install-apt-layer.sh help command works" +else + echo "❌ install-apt-layer.sh help command failed" + exit 1 +fi + +# Create artifacts directory +echo "📁 Creating artifacts directory..." +mkdir -p artifacts +cp apt-layer.sh artifacts/ +cp install-apt-layer.sh artifacts/ + +# Create compilation report +echo "📋 Creating compilation report..." +{ + echo "# apt-layer Compilation Report" + echo "" + echo "**Compilation Date:** $(date)" + echo "**Branch:** $(git branch --show-current)" + echo "**Commit:** $(git rev-parse HEAD)" + echo "" + echo "## File Information" + echo "" + echo "### apt-layer.sh" + echo "- **File:** apt-layer.sh" + echo "- **Size:** $(du -h apt-layer.sh | cut -f1)" + echo "- **Lines:** $(wc -l < apt-layer.sh)" + echo "- **Executable:** $(test -x apt-layer.sh && echo "Yes" || echo "No")" + echo "" + echo "### install-apt-layer.sh" + echo "- **File:** install-apt-layer.sh" + echo "- **Size:** $(du -h install-apt-layer.sh | cut -f1)" + echo "- **Lines:** $(wc -l < install-apt-layer.sh)" + echo "- **Executable:** $(test -x install-apt-layer.sh && echo "Yes" || echo "No")" + echo "" + echo "## Validation Results" + echo "- **apt-layer.sh Syntax Check:** $(bash -n apt-layer.sh && echo "✅ Passed" || echo "❌ Failed")" + echo "- **apt-layer.sh Help Command:** $(./apt-layer.sh --help > /dev/null 2>&1 && echo "✅ Works" || echo "❌ Failed")" + echo "- **install-apt-layer.sh Syntax Check:** $(bash -n install-apt-layer.sh && echo "✅ Passed" || echo "❌ Failed")" + echo "- **install-apt-layer.sh Help Command:** $(./install-apt-layer.sh --help > /dev/null 2>&1 && echo "✅ Works" || echo "❌ Failed")" + echo "" + echo "## Ready for Distribution" + echo "Both compiled scripts are self-contained and ready for use!" +} > COMPILATION_REPORT.md + +cp COMPILATION_REPORT.md artifacts/ + +echo "==========================================" +echo "📦 COMPILATION COMPLETED SUCCESSFULLY" +echo "==========================================" +echo "" +echo "Generated files:" +echo "✅ apt-layer.sh ($(du -h apt-layer.sh | cut -f1))" +echo "✅ install-apt-layer.sh ($(du -h install-apt-layer.sh | cut -f1))" +echo "✅ COMPILATION_REPORT.md" +echo "" +echo "Files are available in the artifacts/ directory" +echo "==========================================" + +# Optional: Commit to repository +read -p "Do you want to commit these compiled scripts to the repository? (y/N): " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "📝 Committing compiled scripts to repository..." + git add apt-layer.sh install-apt-layer.sh + git diff --staged --quiet || git commit -m "Auto-compile apt-layer and installer from local build" + echo "✅ Scripts committed successfully" +else + echo "📝 Skipping commit - scripts are ready for manual commit" +fi + +echo "🎉 Compilation complete!" \ No newline at end of file diff --git a/install-apt-layer.sh b/install-apt-layer.sh index ecfdc87..50f42b7 100644 --- a/install-apt-layer.sh +++ b/install-apt-layer.sh @@ -184,7 +184,7 @@ install_apt_layer() { sudo mkdir -p "$config_dir" # Create paths.json configuration - sudo tee "$config_dir/paths.json" >/dev/null << 'EOF' + sudo tee "$config_dir/paths.json" >/dev/null < "$temp_file" - -# Move to final location -mv "$temp_file" "$OUTPUT_FILE" +# Create the compiled installer by replacing the placeholder using awk +awk -v json="$current_config" ' + /PATHS_JSON_PLACEHOLDER/ { + print json + next + } + { print } +' "$TEMPLATE_FILE" > "$OUTPUT_FILE" # Make it executable chmod +x "$OUTPUT_FILE" diff --git a/src/apt-layer/templates/install-apt-layer.template.sh b/src/apt-layer/templates/install-apt-layer.template.sh index 0fde782..597f5dd 100644 --- a/src/apt-layer/templates/install-apt-layer.template.sh +++ b/src/apt-layer/templates/install-apt-layer.template.sh @@ -184,8 +184,9 @@ install_apt_layer() { sudo mkdir -p "$config_dir" # Create paths.json configuration - sudo tee "$config_dir/paths.json" >/dev/null << 'PATHS_JSON_PLACEHOLDER' + sudo tee "$config_dir/paths.json" >/dev/null <