#!/bin/bash # 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 } # Enhanced OSTree Atomic Workflow for apt-layer # Provides sophisticated atomic package management similar to rpm-ostree # OSTree rebase to new base image ostree_rebase() { local new_base="$1" local deployment_name="${2:-current}" log_info "OSTree rebase to: $new_base" "apt-layer" # Validate new base if ! validate_base_image "$new_base"; then log_error "Invalid base image: $new_base" "apt-layer" return 1 fi # Start transaction start_transaction "ostree-rebase-$deployment_name" # Create new deployment from base local new_deployment="$deployment_name-$(date +%Y%m%d-%H%M%S)" if [[ "$new_base" =~ ^oci:// ]]; then # Rebase to OCI image local image_name="${new_base#oci://}" if ! ostree_rebase_to_oci "$image_name" "$new_deployment"; then rollback_transaction return 1 fi else # Rebase to local ComposeFS image if ! ostree_rebase_to_composefs "$new_base" "$new_deployment"; then rollback_transaction return 1 fi fi # Deploy the new base if ! ostree_deploy "$new_deployment"; then rollback_transaction return 1 fi commit_transaction log_success "OSTree rebase completed: $new_deployment" "apt-layer" return 0 } # OSTree layer packages on current deployment ostree_layer() { local packages=("$@") local deployment_name="${OSTREE_CURRENT_DEPLOYMENT:-current}" log_info "OSTree layer packages: ${packages[*]}" "apt-layer" if [[ ${#packages[@]} -eq 0 ]]; then log_error "No packages specified for layering" "apt-layer" return 1 fi # Start transaction start_transaction "ostree-layer-$deployment_name" # Create new deployment with layered packages local new_deployment="$deployment_name-layered-$(date +%Y%m%d-%H%M%S)" if ! ostree_create_layered_deployment "$deployment_name" "$new_deployment" "${packages[@]}"; then rollback_transaction return 1 fi # Deploy the layered deployment if ! ostree_deploy "$new_deployment"; then rollback_transaction return 1 fi commit_transaction log_success "OSTree layer completed: $new_deployment" "apt-layer" return 0 } # OSTree override package in deployment ostree_override() { local package_name="$1" local override_path="$2" local deployment_name="${OSTREE_CURRENT_DEPLOYMENT:-current}" log_info "OSTree override package: $package_name with $override_path" "apt-layer" if [[ -z "$package_name" ]] || [[ -z "$override_path" ]]; then log_error "Package name and override path required" "apt-layer" return 1 fi if [[ ! -f "$override_path" ]]; then log_error "Override package not found: $override_path" "apt-layer" return 1 fi # Start transaction start_transaction "ostree-override-$deployment_name" # Create new deployment with package override local new_deployment="$deployment_name-override-$(date +%Y%m%d-%H%M%S)" if ! ostree_create_override_deployment "$deployment_name" "$new_deployment" "$package_name" "$override_path"; then rollback_transaction return 1 fi # Deploy the override deployment if ! ostree_deploy "$new_deployment"; then rollback_transaction return 1 fi commit_transaction log_success "OSTree override completed: $new_deployment" "apt-layer" return 0 } # OSTree deploy deployment ostree_deploy() { local deployment_name="$1" log_info "OSTree deploy: $deployment_name" "apt-layer" if [[ -z "$deployment_name" ]]; then log_error "Deployment name required" "apt-layer" return 1 fi # Validate deployment exists if ! ostree_deployment_exists "$deployment_name"; then log_error "Deployment not found: $deployment_name" "apt-layer" return 1 fi # Perform atomic deployment if ! atomic_deploy_deployment "$deployment_name"; then log_error "Failed to deploy: $deployment_name" "apt-layer" return 1 fi # Update current deployment reference OSTREE_CURRENT_DEPLOYMENT="$deployment_name" log_success "OSTree deploy completed: $deployment_name" "apt-layer" return 0 } # OSTree compose tree (declarative image building) ostree_compose_tree() { local config_file="$1" log_info "OSTree compose tree from: $config_file" "apt-layer" if [[ -z "$config_file" ]] || [[ ! -f "$config_file" ]]; then log_error "Valid configuration file required" "apt-layer" return 1 fi # Parse configuration if ! parse_compose_config "$config_file"; then log_error "Failed to parse configuration: $config_file" "apt-layer" return 1 fi # Start transaction start_transaction "ostree-compose-tree" # Build tree from configuration if ! build_tree_from_config; then rollback_transaction return 1 fi commit_transaction log_success "OSTree compose tree completed" "apt-layer" return 0 } # Helper functions for OSTree operations # Rebase to OCI image ostree_rebase_to_oci() { local image_name="$1" local deployment_name="$2" log_debug "Rebasing to OCI image: $image_name" "apt-layer" # Import OCI image as ComposeFS local composefs_image="$WORKSPACE/images/$deployment_name" if ! import_oci_image "$image_name" "$composefs_image"; then log_error "Failed to import OCI image: $image_name" "apt-layer" return 1 fi # Create deployment from ComposeFS image if ! create_deployment_from_composefs "$composefs_image" "$deployment_name"; then log_error "Failed to create deployment from ComposeFS" "apt-layer" return 1 fi return 0 } # Rebase to ComposeFS image ostree_rebase_to_composefs() { local base_image="$1" local deployment_name="$2" log_debug "Rebasing to ComposeFS image: $base_image" "apt-layer" # Validate base image exists if ! composefs_image_exists "$base_image"; then log_error "Base image not found: $base_image" "apt-layer" return 1 fi # Create deployment from base image if ! create_deployment_from_composefs "$base_image" "$deployment_name"; then log_error "Failed to create deployment from base image" "apt-layer" return 1 fi return 0 } # Create layered deployment ostree_create_layered_deployment() { local base_deployment="$1" local new_deployment="$2" shift 2 local packages=("$@") log_debug "Creating layered deployment: $base_deployment -> $new_deployment" "apt-layer" # Get base deployment path local base_path base_path=$(get_deployment_path "$base_deployment") if [[ -z "$base_path" ]]; then log_error "Base deployment not found: $base_deployment" "apt-layer" return 1 fi # Create new deployment with layered packages if ! create_layer_on_deployment "$base_path" "$new_deployment" "${packages[@]}"; then log_error "Failed to create layered deployment" "apt-layer" return 1 fi return 0 } # Create override deployment ostree_create_override_deployment() { local base_deployment="$1" local new_deployment="$2" local package_name="$3" local override_path="$4" log_debug "Creating override deployment: $base_deployment -> $new_deployment" "apt-layer" # Get base deployment path local base_path base_path=$(get_deployment_path "$base_deployment") if [[ -z "$base_path" ]]; then log_error "Base deployment not found: $base_deployment" "apt-layer" return 1 fi # Create new deployment with package override if ! create_override_on_deployment "$base_path" "$new_deployment" "$package_name" "$override_path"; then log_error "Failed to create override deployment" "apt-layer" return 1 fi return 0 } # Parse compose configuration parse_compose_config() { local config_file="$1" log_debug "Parsing compose configuration: $config_file" "apt-layer" # Load configuration using jq if ! command -v jq &> /dev/null; then log_error "jq required for configuration parsing" "apt-layer" return 1 fi # Parse configuration structure COMPOSE_CONFIG=$(jq -r '.' "$config_file") if [[ $? -ne 0 ]]; then log_error "Failed to parse configuration file" "apt-layer" return 1 fi # Extract configuration values COMPOSE_BASE_IMAGE=$(echo "$COMPOSE_CONFIG" | jq -r '.base-image // empty') COMPOSE_LAYERS=$(echo "$COMPOSE_CONFIG" | jq -r '.layers[]? // empty') COMPOSE_OVERRIDES=$(echo "$COMPOSE_CONFIG" | jq -r '.overrides[]? // empty') log_debug "Configuration parsed: base=$COMPOSE_BASE_IMAGE, layers=${#COMPOSE_LAYERS[@]}, overrides=${#COMPOSE_OVERRIDES[@]}" "apt-layer" return 0 } # Build tree from configuration build_tree_from_config() { log_debug "Building tree from configuration" "apt-layer" # Start with base image if [[ -n "$COMPOSE_BASE_IMAGE" ]]; then if ! ostree_rebase_to_oci "$COMPOSE_BASE_IMAGE" "compose-base"; then log_error "Failed to create base from configuration" "apt-layer" return 1 fi fi # Add layers if [[ -n "$COMPOSE_LAYERS" ]]; then local layer_packages=() while IFS= read -r package; do if [[ -n "$package" ]]; then layer_packages+=("$package") fi done <<< "$COMPOSE_LAYERS" if [[ ${#layer_packages[@]} -gt 0 ]]; then if ! ostree_layer "${layer_packages[@]}"; then log_error "Failed to add layers from configuration" "apt-layer" return 1 fi fi fi # Apply overrides if [[ -n "$COMPOSE_OVERRIDES" ]]; then while IFS= read -r override; do if [[ -n "$override" ]]; then local package_name local override_path package_name=$(echo "$override" | jq -r '.package // empty') override_path=$(echo "$override" | jq -r '.with // empty') if [[ -n "$package_name" ]] && [[ -n "$override_path" ]]; then if ! ostree_override "$package_name" "$override_path"; then log_error "Failed to apply override: $package_name" "apt-layer" return 1 fi fi fi done <<< "$COMPOSE_OVERRIDES" fi return 0 } # Enhanced package management with metadata handling # Layer package with metadata preservation ostree_layer_with_metadata() { local package="$1" local deployment_name="${OSTREE_CURRENT_DEPLOYMENT:-current}" local preserve_metadata="${2:-true}" local resolve_conflicts="${3:-keep-latest}" log_info "OSTree layer with metadata: $package" "apt-layer" # Start transaction start_transaction "ostree-layer-metadata-$deployment_name" # Create new deployment with metadata handling local new_deployment="$deployment_name-metadata-$(date +%Y%m%d-%H%M%S)" if ! ostree_create_metadata_aware_deployment "$deployment_name" "$new_deployment" "$package" "$preserve_metadata" "$resolve_conflicts"; then rollback_transaction return 1 fi # Deploy the new deployment if ! ostree_deploy "$new_deployment"; then rollback_transaction return 1 fi commit_transaction log_success "OSTree layer with metadata completed: $new_deployment" "apt-layer" return 0 } # Multi-arch aware layering ostree_layer_multiarch() { local package="$1" local arch="${2:-amd64}" local multiarch_type="${3:-same}" local deployment_name="${OSTREE_CURRENT_DEPLOYMENT:-current}" log_info "OSTree layer multi-arch: $package ($arch, $multiarch_type)" "apt-layer" # Validate multi-arch parameters case "$multiarch_type" in same|foreign|allowed) ;; *) log_error "Invalid multi-arch type: $multiarch_type" "apt-layer" return 1 ;; esac # Start transaction start_transaction "ostree-layer-multiarch-$deployment_name" # Create new deployment with multi-arch support local new_deployment="$deployment_name-multiarch-$(date +%Y%m%d-%H%M%S)" if ! ostree_create_multiarch_deployment "$deployment_name" "$new_deployment" "$package" "$arch" "$multiarch_type"; then rollback_transaction return 1 fi # Deploy the new deployment if ! ostree_deploy "$new_deployment"; then rollback_transaction return 1 fi commit_transaction log_success "OSTree layer multi-arch completed: $new_deployment" "apt-layer" return 0 } # Maintainer script handling ostree_layer_with_script_validation() { local package="$1" local script_context="${2:-offline}" local deployment_name="${OSTREE_CURRENT_DEPLOYMENT:-current}" log_info "OSTree layer with script validation: $package ($script_context)" "apt-layer" # Validate maintainer scripts if ! validate_maintainer_scripts "$package" "$script_context"; then log_error "Maintainer script validation failed for: $package" "apt-layer" return 1 fi # Start transaction start_transaction "ostree-layer-scripts-$deployment_name" # Create new deployment with script handling local new_deployment="$deployment_name-scripts-$(date +%Y%m%d-%H%M%S)" if ! ostree_create_script_aware_deployment "$deployment_name" "$new_deployment" "$package" "$script_context"; then rollback_transaction return 1 fi # Deploy the new deployment if ! ostree_deploy "$new_deployment"; then rollback_transaction return 1 fi commit_transaction log_success "OSTree layer with script validation completed: $new_deployment" "apt-layer" return 0 } # Validate maintainer scripts validate_maintainer_scripts() { local package="$1" local script_context="$2" log_debug "Validating maintainer scripts for: $package ($script_context)" "apt-layer" # Extract package and examine maintainer scripts local temp_dir temp_dir=$(mktemp -d) # Download package if ! apt-get download "$package" -o Dir::Cache="$temp_dir"; then log_error "Failed to download package for script validation: $package" "apt-layer" rm -rf "$temp_dir" return 1 fi # Extract control information local deb_file deb_file=$(find "$temp_dir" -name "*.deb" | head -1) if [[ -z "$deb_file" ]]; then log_error "No .deb file found for script validation" "apt-layer" rm -rf "$temp_dir" return 1 fi # Extract control scripts local control_dir="$temp_dir/control" mkdir -p "$control_dir" if ! dpkg-deb -e "$deb_file" "$control_dir"; then log_error "Failed to extract control information" "apt-layer" rm -rf "$temp_dir" return 1 fi # Check for problematic scripts local problematic_scripts=() # Check for service management scripts if [[ -f "$control_dir/postinst" ]] && grep -q "systemctl" "$control_dir/postinst"; then problematic_scripts+=("postinst:systemctl") fi # Check for user interaction scripts if [[ -f "$control_dir/postinst" ]] && grep -q "debconf" "$control_dir/postinst"; then problematic_scripts+=("postinst:debconf") fi # Check for live system state dependencies if [[ -f "$control_dir/postinst" ]] && grep -q "/proc\|/sys" "$control_dir/postinst"; then problematic_scripts+=("postinst:live-state") fi # Report problematic scripts if [[ ${#problematic_scripts[@]} -gt 0 ]]; then log_warning "Problematic maintainer scripts detected in $package:" "apt-layer" for script in "${problematic_scripts[@]}"; do log_warning " - $script" "apt-layer" done if [[ "$script_context" == "strict" ]]; then log_error "Script validation failed in strict mode" "apt-layer" rm -rf "$temp_dir" return 1 fi fi # Cleanup rm -rf "$temp_dir" log_debug "Maintainer script validation passed for: $package" "apt-layer" return 0 }