particle-os-tools/orchestrator.sh
robojerk 74c7bede5f Initial commit: Particle-OS tools repository
- Complete Particle-OS rebranding from uBlue-OS
- Professional installation system with standardized paths
- Self-initialization system with --init and --reset commands
- Enhanced error messages and dependency checking
- Comprehensive testing infrastructure
- All source scriptlets updated with runtime improvements
- Clean codebase with redundant files moved to archive
- Complete documentation suite
2025-07-11 21:14:33 -07:00

824 lines
No EOL
32 KiB
Bash

#!/bin/bash
# orchestrator.sh - Particle-OS System Orchestrator
# This script acts as the central hub for managing an immutable Particle-OS system
# by orchestrating operations across apt-layer.sh, composefs-alternative.sh,
# fsverity-alternative.sh, and bootupd-alternative.sh.
set -euo pipefail
# --- Configuration ---
# Load Particle-OS configuration if available
if [[ -f "/usr/local/etc/particle-config.sh" ]]; then
source "/usr/local/etc/particle-config.sh"
else
# Fallback configuration if particle-config.sh is not available
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"
fi
# Define the root directory for all Particle-OS related data
PARTICLE_OS_ROOT="${PARTICLE_WORKSPACE:-/var/lib/particle-os}"
# Paths to your alternative scripts (standardized installation)
APT_LAYER_SCRIPT="/usr/local/bin/apt-layer"
COMPOSEFS_SCRIPT="/usr/local/bin/composefs"
FSVERITY_SCRIPT="fsverity"
BOOTUPD_SCRIPT="/usr/local/bin/bootupd"
# Transaction log and state files (centralized for the orchestrator)
TRANSACTION_LOG="${PARTICLE_LOG_DIR:-/var/log/particle-os}/orchestrator_transaction.log"
TRANSACTION_STATE="${PARTICLE_WORKSPACE:-/var/lib/particle-os}/orchestrator_transaction.state"
# --- Global Transaction State Variables ---
TRANSACTION_ID=""
TRANSACTION_OPERATION=""
TRANSACTION_TARGET=""
TRANSACTION_PHASE=""
TRANSACTION_BACKUP_PATH="" # Path to a backup created during a critical step
TRANSACTION_TEMP_DIRS=() # List of temporary directories to clean up
# --- Colors for Output ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# --- Logging Functions ---
log_info() {
echo -e "${BLUE}[INFO]${NC} [$(date +'%H:%M:%S')] $1" | tee -a "$TRANSACTION_LOG"
}
log_debug() {
echo -e "${YELLOW}[DEBUG]${NC} [$(date +'%H:%M:%S')] $1" | tee -a "$TRANSACTION_LOG"
}
log_error() {
echo -e "${RED}[ERROR]${NC} [$(date +'%H:%M:%S')] $1" | tee -a "$TRANSACTION_LOG"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} [$(date +'%H:%M:%S')] $1" | tee -a "$TRANSACTION_LOG"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} [$(date +'%H:%M:%S')] $1" | tee -a "$TRANSACTION_LOG"
}
log_orchestrator() {
echo -e "${PURPLE}[ORCHESTRATOR]${NC} [$(date +'%H:%M:%S')] $1" | tee -a "$TRANSACTION_LOG"
}
# --- Transaction Management Functions ---
# Starts a new transaction
start_transaction() {
local operation="$1"
local target="$2"
TRANSACTION_ID=$(date +%Y%m%d_%H%M%S)_$$
TRANSACTION_OPERATION="$operation"
TRANSACTION_TARGET="$target"
TRANSACTION_PHASE="started"
TRANSACTION_BACKUP_PATH=""
TRANSACTION_TEMP_DIRS=()
log_orchestrator "Starting transaction $TRANSACTION_ID: $operation -> $target"
# Write initial state to file
save_transaction_state
log_orchestrator "Transaction $TRANSACTION_ID started successfully."
}
# Updates the current phase of the transaction
update_transaction_phase() {
local phase="$1"
TRANSACTION_PHASE="$phase"
log_orchestrator "Transaction $TRANSACTION_ID phase: $phase"
save_transaction_state
}
# Saves the current transaction state to file
save_transaction_state() {
cat > "$TRANSACTION_STATE" << EOF
TRANSACTION_ID=$TRANSACTION_ID
OPERATION=$TRANSACTION_OPERATION
TARGET=$TRANSACTION_TARGET
PHASE=$TRANSACTION_PHASE
BACKUP_PATH=$TRANSACTION_BACKUP_PATH
TEMP_DIRS=${TRANSACTION_TEMP_DIRS[*]}
START_TIME=$(date -Iseconds)
EOF
}
# Clears the transaction state files
clear_transaction_state() {
TRANSACTION_ID=""
TRANSACTION_OPERATION=""
TRANSACTION_TARGET=""
TRANSACTION_PHASE=""
TRANSACTION_BACKUP_PATH=""
TRANSACTION_TEMP_DIRS=()
rm -f "$TRANSACTION_STATE"
log_orchestrator "Transaction state cleared."
}
# Commits the transaction, indicating success
commit_transaction() {
if [[ -n "$TRANSACTION_ID" ]]; then
update_transaction_phase "committed"
echo "END_TIME=$(date -Iseconds)" >> "$TRANSACTION_LOG"
echo "STATUS=success" >> "$TRANSACTION_LOG"
log_success "Transaction $TRANSACTION_ID completed successfully."
clear_transaction_state
fi
}
# Rolls back the transaction in case of failure
rollback_transaction() {
if [[ -n "$TRANSACTION_ID" ]]; then
log_warning "Rolling back transaction $TRANSACTION_ID (Operation: $TRANSACTION_OPERATION, Phase: $TRANSACTION_PHASE)"
# --- Rollback logic based on phase ---
case "$TRANSACTION_OPERATION" in
"install_packages"|"rebase_system")
case "$TRANSACTION_PHASE" in
"build_rootfs")
log_info "Build was interrupted. Temporary build directory will be cleaned."
# No specific rollback needed beyond temp dir cleanup
;;
"create_composefs_image")
log_info "ComposeFS image creation failed. Attempting to remove partial image."
# If image creation failed, try to remove the partial image
# TODO: composefs-alternative.sh needs a 'remove-partial' or 'cleanup-failed-image' command
# For now, rely on cleanup_on_exit for temp dirs.
;;
"verify_image")
log_info "Image verification failed. Image might be present but untrusted."
# No specific rollback needed beyond cleanup_on_exit
;;
"deploy_bootloader")
log_info "Bootloader deployment failed. Attempting to revert boot entry."
# TODO: bootupd-alternative.sh needs a 'rollback' or 'revert-default' command
# This would revert the default boot entry to the previous known good one.
# For now, user might need manual intervention or rely on previous boot entries.
;;
*)
log_warning "No specific rollback action defined for phase: $TRANSACTION_PHASE"
;;
esac
;;
# Add more operation types here
*)
log_warning "No specific rollback action defined for operation: $TRANSACTION_OPERATION"
;;
esac
# Clean up all temporary directories regardless of phase
for temp_dir in "${TRANSACTION_TEMP_DIRS[@]}"; do
if [[ -d "$temp_dir" ]]; then
log_debug "Cleaning up temporary directory: $temp_dir"
rm -rf "$temp_dir" 2>/dev/null || true
fi
done
# Update transaction log
echo "END_TIME=$(date -Iseconds)" >> "$TRANSACTION_LOG"
echo "STATUS=rolled_back" >> "$TRANSACTION_LOG"
log_orchestrator "Transaction $TRANSACTION_ID rolled back."
clear_transaction_state
fi
}
# Checks for incomplete transactions on startup and prompts user
check_incomplete_transactions() {
if [[ -f "$TRANSACTION_STATE" ]]; then
log_warning "Found incomplete transaction state from previous run."
# Source the state file to load transaction details
source "$TRANSACTION_STATE"
log_info "Incomplete transaction ID: $TRANSACTION_ID"
log_info "Operation: $TRANSACTION_OPERATION"
log_info "Target: $TRANSACTION_TARGET"
log_info "Last completed phase: $TRANSACTION_PHASE"
echo
echo "Options:"
echo "1. Attempt to resume transaction (if supported for this phase)"
echo "2. Rollback transaction (discard changes and clean up)"
echo "3. Clear transaction state (manual cleanup might be required)"
echo "4. Exit (resolve manually)"
echo
read -p "Choose option (1-4): " choice
case $choice in
1)
log_info "Attempting to resume transaction..."
# Resume logic needs to be implemented per operation and phase
log_error "Resume functionality not fully implemented for this operation/phase. Please choose another option."
exit 1 # For now, force user to choose another option
;;
2)
log_info "Rolling back transaction..."
rollback_transaction
;;
3)
log_info "Clearing transaction state..."
clear_transaction_state
;;
4)
log_error "Exiting due to incomplete transaction. Please resolve manually."
exit 1
;;
*)
log_error "Invalid choice. Exiting."
exit 1
;;
esac
fi
}
# --- Helper Functions ---
# Check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root."
exit 1
fi
}
# Check if all required scripts exist and are executable
check_script_dependencies() {
log_orchestrator "Checking core Particle-OS script dependencies..."
# Check if Particle-OS configuration is available
if [[ ! -f "/usr/local/etc/particle-config.sh" ]]; then
log_warning "Particle-OS configuration not found at /usr/local/etc/particle-config.sh"
log_info "Using fallback configuration paths"
fi
local missing=()
# Check Particle-OS scripts
for script in "$APT_LAYER_SCRIPT" "$COMPOSEFS_SCRIPT" "$BOOTUPD_SCRIPT"; do
if [[ ! -f "$script" ]]; then
missing+=("$script (file not found)")
elif [[ ! -x "$script" ]]; then
missing+=("$script (not executable)")
fi
done
# Check system fsverity command
if ! command -v fsverity >/dev/null 2>&1; then
missing+=("fsverity (system command not found)")
log_info "fsverity not found. Install with: sudo apt install -y fsverity"
fi
if [[ ${#missing[@]} -gt 0 ]]; then
log_error "Missing or non-executable core scripts: ${missing[*]}"
log_error "Please ensure all Particle-OS alternative scripts are in /usr/local/bin and are executable."
log_error "For fsverity, install with: sudo apt install -y fsverity"
log_error "Run 'sudo apt-layer --init' to initialize the Particle-OS environment."
exit 1
fi
log_success "All core script dependencies found and are executable."
}
# Initialize the Particle-OS workspace
init_workspace() {
log_orchestrator "Initializing Particle-OS workspace..."
# Create main workspace directory
mkdir -p "$PARTICLE_OS_ROOT"
# Create subdirectories using Particle-OS configuration
mkdir -p "${PARTICLE_BUILD_DIR:-$PARTICLE_OS_ROOT/build}"
mkdir -p "${PARTICLE_TEMP_DIR:-$PARTICLE_OS_ROOT/temp}"
mkdir -p "${PARTICLE_LAYERS_DIR:-$PARTICLE_OS_ROOT/layers}"
mkdir -p "${PARTICLE_LOG_DIR:-/var/log/particle-os}"
mkdir -p "${PARTICLE_CACHE_DIR:-/var/cache/particle-os}"
# Ensure transaction log directory exists and is writable
mkdir -p "$(dirname "$TRANSACTION_LOG")"
touch "$TRANSACTION_LOG"
chmod 644 "$TRANSACTION_LOG" # Allow non-root to read logs
log_success "Workspace initialized at $PARTICLE_OS_ROOT"
}
# Run a sub-script and check its exit code
run_script() {
local script_path="$1"
shift
local script_args=("$@")
log_debug "Running: $script_path ${script_args[*]}"
# Execute the script, redirecting its output to our log
if ! "$script_path" "${script_args[@]}" >> "$TRANSACTION_LOG" 2>&1; then
log_error "Script '$script_path' failed with arguments: ${script_args[*]}"
return 1
fi
log_debug "Script '$script_path' completed successfully."
return 0
}
# Get the current booted Particle-OS image ID
get_current_particle_os_image_id() {
# TODO: This needs to be implemented based on how bootupd-alternative.sh
# tracks the currently booted composefs image.
# For now, return a placeholder or an error if not implemented.
log_warning "get_current_particle_os_image_id is a placeholder. Returning dummy ID."
# Dummy IDs reflecting the new naming conventions
# Particle-OS Corona: KDE Plasma desktop
# Particle-OS Apex: GNOME desktop
echo "particle-os-base-24.04-corona"
}
# Enhanced package installation with dpkg support
install_packages_enhanced() {
local base_image_name="$1"
local use_dpkg="${2:-false}"
shift 2
local packages=("$@")
if [[ -z "$base_image_name" || ${#packages[@]} -eq 0 ]]; then
log_error "Usage: install <base_image_name> [--dpkg] <package1> [package2]..."
exit 1
fi
local new_image_name="particle-os-custom-$(date +%Y%m%d%H%M%S)"
local temp_rootfs_dir="${PARTICLE_BUILD_DIR:-$PARTICLE_OS_ROOT/build}/temp-rootfs-${TRANSACTION_ID}"
log_orchestrator "Starting enhanced package installation for '$new_image_name' based on '$base_image_name'."
if [[ "$use_dpkg" == "true" ]]; then
log_info "Using dpkg-based installation for better performance"
fi
start_transaction "install_packages_enhanced" "$new_image_name"
# Add temp_rootfs_dir to cleanup list
TRANSACTION_TEMP_DIRS+=("$temp_rootfs_dir")
mkdir -p "$temp_rootfs_dir"
# Phase 1: Build the new root filesystem with packages using apt-layer.sh
update_transaction_phase "build_rootfs"
log_orchestrator "Building new root filesystem with packages: ${packages[*]}..."
# Check if base image exists in composefs-alternative.sh
if ! "$COMPOSEFS_SCRIPT" list-images | grep -q "$base_image_name"; then
log_error "Base image '$base_image_name' not found in composefs-alternative.sh."
exit 1
fi
# Mount the base image to get its content
local base_mount_point="${PARTICLE_TEMP_DIR:-$PARTICLE_OS_ROOT/temp}/temp_base_mount_${TRANSACTION_ID}"
TRANSACTION_TEMP_DIRS+=("$base_mount_point")
mkdir -p "$base_mount_point"
if ! run_script "$COMPOSEFS_SCRIPT" mount "$base_image_name" "$base_mount_point"; then
log_error "Failed to mount base image '$base_image_name'."
exit 1
fi
# Copy base image content to temp_rootfs_dir
log_info "Copying base image content to temporary build directory..."
if ! rsync -a "$base_mount_point/" "$temp_rootfs_dir/"; then
log_error "Failed to copy base image content."
exit 1
fi
# Unmount base image (cleanup)
if ! run_script "$COMPOSEFS_SCRIPT" unmount "$base_mount_point"; then
log_warning "Failed to unmount temporary base image mount point: $base_mount_point"
fi
# Install packages using the appropriate method
if [[ "$use_dpkg" == "true" ]]; then
log_info "Using dpkg-based package installation..."
# Use apt-layer's dpkg functionality
if ! run_script "$APT_LAYER_SCRIPT" --dpkg-install "${packages[@]}"; then
log_error "dpkg-based package installation failed."
exit 1
fi
else
log_info "Using traditional apt-based package installation..."
# Traditional apt-get installation
if ! chroot "$temp_rootfs_dir" apt-get update; then
log_error "apt-get update failed in chroot."
exit 1
fi
if ! chroot "$temp_rootfs_dir" apt-get install -y "${packages[@]}"; then
log_error "apt-get install failed in chroot."
exit 1
fi
chroot "$temp_rootfs_dir" apt-get clean
chroot "$temp_rootfs_dir" apt-get autoremove -y
fi
log_success "Root filesystem built in: $temp_rootfs_dir"
# Phase 2: Create the ComposeFS image from the new rootfs
update_transaction_phase "create_composefs_image"
log_orchestrator "Creating ComposeFS image '$new_image_name' from '$temp_rootfs_dir'..."
if ! run_script "$COMPOSEFS_SCRIPT" create "$new_image_name" "$temp_rootfs_dir"; then
log_error "Failed to create ComposeFS image."
exit 1
fi
log_success "ComposeFS image '$new_image_name' created."
# Phase 3: Verify and sign the new ComposeFS image
update_transaction_phase "verify_image"
log_orchestrator "Verifying and signing ComposeFS image '$new_image_name'..."
if ! run_script "$FSVERITY_SCRIPT" enable "${PARTICLE_IMAGES_DIR:-$PARTICLE_OS_ROOT/images}/$new_image_name" sha256; then
log_error "Failed to verify/sign ComposeFS image."
exit 1
fi
log_success "ComposeFS image '$new_image_name' verified/signed."
# Phase 4: Deploy the new image via the bootloader
update_transaction_phase "deploy_bootloader"
log_orchestrator "Deploying new image '$new_image_name' via bootloader..."
if ! run_script "$BOOTUPD_SCRIPT" set-default "$new_image_name"; then
log_error "Failed to deploy image via bootloader."
exit 1
fi
log_success "Image '$new_image_name' deployed as default boot entry."
# Commit the transaction
commit_transaction
log_orchestrator "Enhanced package installation and system update completed for '$new_image_name'."
log_info "A reboot is recommended to apply changes."
}
# --- Core Orchestration Workflows ---
# Installs packages by building a new rootfs, creating a composefs image,
# verifying it, and deploying it via the bootloader.
install_packages() {
local base_image_name="$1"
shift
local packages=("$@")
if [[ -z "$base_image_name" || ${#packages[@]} -eq 0 ]]; then
log_error "Usage: install <base_image_name> <package1> [package2]..."
exit 1
fi
local new_image_name="particle-os-custom-$(date +%Y%m%d%H%M%S)"
# Example for specific desktop images:
# local new_image_name="particle-os-corona-$(date +%Y%m%d%H%M%S)" # For KDE Plasma
# local new_image_name="particle-os-apex-$(date +%Y%m%d%H%M%S)" # For GNOME
local temp_rootfs_dir="${PARTICLE_BUILD_DIR:-$PARTICLE_OS_ROOT/build}/temp-rootfs-${TRANSACTION_ID}"
log_orchestrator "Starting package installation for '$new_image_name' based on '$base_image_name'."
start_transaction "install_packages" "$new_image_name"
# Add temp_rootfs_dir to cleanup list
TRANSACTION_TEMP_DIRS+=("$temp_rootfs_dir")
mkdir -p "$temp_rootfs_dir"
# Phase 1: Build the new root filesystem with packages using apt-layer.sh
update_transaction_phase "build_rootfs"
log_orchestrator "Building new root filesystem with packages: ${packages[*]}..."
# Check if base image exists in composefs-alternative.sh
if ! "$COMPOSEFS_SCRIPT" list-images | grep -q "$base_image_name"; then
log_error "Base image '$base_image_name' not found in composefs-alternative.sh."
exit 1
fi
# Mount the base image to get its content (simulating apt-layer's initial checkout)
local base_mount_point="${PARTICLE_TEMP_DIR:-$PARTICLE_OS_ROOT/temp}/temp_base_mount_${TRANSACTION_ID}"
TRANSACTION_TEMP_DIRS+=("$base_mount_point")
mkdir -p "$base_mount_point"
if ! run_script "$COMPOSEFS_SCRIPT" mount "$base_image_name" "$base_mount_point"; then
log_error "Failed to mount base image '$base_image_name'."
exit 1
fi
# Copy base image content to temp_rootfs_dir (this is where apt-layer would work)
log_info "Copying base image content to temporary build directory..."
if ! rsync -a "$base_mount_point/" "$temp_rootfs_dir/"; then
log_error "Failed to copy base image content."
exit 1
fi
# Unmount base image (cleanup)
if ! run_script "$COMPOSEFS_SCRIPT" unmount "$base_mount_point"; then
log_warning "Failed to unmount temporary base image mount point: $base_mount_point"
fi
# Now, simulate apt-layer.sh installing packages into temp_rootfs_dir
log_info "Simulating package installation into $temp_rootfs_dir (chroot apt-get)..."
# This is a placeholder for apt-layer's actual package installation logic
# In reality, apt-layer.sh --build-rootfs would handle this.
if ! chroot "$temp_rootfs_dir" apt-get update; then
log_error "Simulated apt-get update failed in chroot."
exit 1
fi
if ! chroot "$temp_rootfs_dir" apt-get install -y "${packages[@]}"; then
log_error "Simulated apt-get install failed in chroot."
exit 1
fi
chroot "$temp_rootfs_dir" apt-get clean
chroot "$temp_rootfs_dir" apt-get autoremove -y
log_success "Root filesystem built in: $temp_rootfs_dir"
# Phase 2: Create the ComposeFS image from the new rootfs
update_transaction_phase "create_composefs_image"
log_orchestrator "Creating ComposeFS image '$new_image_name' from '$temp_rootfs_dir'..."
if ! run_script "$COMPOSEFS_SCRIPT" create "$new_image_name" "$temp_rootfs_dir"; then
log_error "Failed to create ComposeFS image."
exit 1
fi
log_success "ComposeFS image '$new_image_name' created."
# Phase 3: Verify and sign the new ComposeFS image
update_transaction_phase "verify_image"
log_orchestrator "Verifying and signing ComposeFS image '$new_image_name'..."
# TODO: fsverity-alternative.sh needs a command to verify a composefs image by name
# and potentially sign it. Assuming 'enable' can work on a created image.
if ! run_script "$FSVERITY_SCRIPT" enable "${PARTICLE_IMAGES_DIR:-$PARTICLE_OS_ROOT/images}/$new_image_name" sha256; then # Assuming fsverity works on the image dir
log_error "Failed to verify/sign ComposeFS image."
exit 1
fi
log_success "ComposeFS image '$new_image_name' verified/signed."
# Phase 4: Deploy the new image via the bootloader
update_transaction_phase "deploy_bootloader"
log_orchestrator "Deploying new image '$new_image_name' via bootloader..."
# TODO: bootupd-alternative.sh needs a command to register and set default
# a composefs image by name/ID. Assuming 'set-default' can take an image name.
if ! run_script "$BOOTUPD_SCRIPT" set-default "$new_image_name"; then
log_error "Failed to deploy image via bootloader."
exit 1
fi
log_success "Image '$new_image_name' deployed as default boot entry."
# Commit the transaction
commit_transaction
log_orchestrator "Package installation and system update completed for '$new_image_name'."
log_info "A reboot is recommended to apply changes."
}
# Rebases the system to a new base image
rebase_system() {
local new_base_image="$1"
if [[ -z "$new_base_image" ]]; then
log_error "Usage: rebase <new_base_image_name>"
exit 1
fi
log_orchestrator "Starting rebase operation to '$new_base_image'."
start_transaction "rebase_system" "$new_base_image"
# Phase 1: Check if new base image exists
update_transaction_phase "check_base_image"
log_orchestrator "Checking if new base image '$new_base_image' exists..."
if ! "$COMPOSEFS_SCRIPT" list-images | grep -q "$new_base_image"; then
log_error "New base image '$new_base_image' not found in composefs-alternative.sh."
exit 1
fi
log_success "New base image '$new_base_image' found."
# Phase 2: Get current layered packages and re-apply them on the new base
update_transaction_phase "reapply_layers"
log_orchestrator "Re-applying existing layers on top of '$new_base_image'..."
# This is highly complex. It would involve:
# 1. Identifying packages/changes in the *current* system's layered image.
# 2. Calling apt-layer.sh --build-rootfs with the new_base_image and these identified packages.
# This part needs significant design and implementation to handle package manifests.
log_warning "Re-applying layers is a complex feature and is not yet implemented."
log_warning "For now, rebase will just switch the base image, potentially losing layered packages."
local temp_rootfs_dir="${PARTICLE_BUILD_DIR:-$PARTICLE_OS_ROOT/build}/temp-rebase-rootfs-${TRANSACTION_ID}"
TRANSACTION_TEMP_DIRS+=("$temp_rootfs_dir")
mkdir -p "$temp_rootfs_dir"
# Mount the new base image to get its content
local base_mount_point="${PARTICLE_TEMP_DIR:-$PARTICLE_OS_ROOT/temp}/temp_rebase_base_mount_${TRANSACTION_ID}"
TRANSACTION_TEMP_DIRS+=("$base_mount_point")
mkdir -p "$base_mount_point"
if ! run_script "$COMPOSEFS_SCRIPT" mount "$new_base_image" "$base_mount_point"; then
log_error "Failed to mount new base image '$new_base_image'."
exit 1
fi
# Copy new base image content to temp_rootfs_dir
log_info "Copying new base image content to temporary rebase directory..."
if ! rsync -a "$base_mount_point/" "$temp_rootfs_dir/"; then
log_error "Failed to copy new base image content."
exit 1
fi
# Unmount base image (cleanup)
if ! run_script "$COMPOSEFS_SCRIPT" unmount "$base_mount_point"; then
log_warning "Failed to unmount temporary base image mount point: $base_mount_point"
fi
# Phase 3: Create new ComposeFS image for the rebased system
update_transaction_phase "create_rebased_image"
local rebased_image_name="${new_base_image}-rebased-$(date +%Y%m%d%H%M%S)"
log_orchestrator "Creating rebased ComposeFS image '$rebased_image_name'..."
if ! run_script "$COMPOSEFS_SCRIPT" create "$rebased_image_name" "$temp_rootfs_dir"; then
log_error "Failed to create rebased ComposeFS image."
exit 1
fi
log_success "Rebased ComposeFS image '$rebased_image_name' created."
# Phase 4: Verify and sign the rebased image
update_transaction_phase "verify_rebased_image"
log_orchestrator "Verifying and signing rebased ComposeFS image '$rebased_image_name'..."
if ! run_script "$FSVERITY_SCRIPT" enable "${PARTICLE_IMAGES_DIR:-$PARTICLE_OS_ROOT/images}/$rebased_image_name" sha256; then
log_error "Failed to verify/sign rebased ComposeFS image."
exit 1
fi
log_success "Rebased ComposeFS image '$rebased_image_name' verified/signed."
# Phase 5: Deploy the rebased image via the bootloader
update_transaction_phase "deploy_rebased_bootloader"
log_orchestrator "Deploying rebased image '$rebased_image_name' via bootloader..."
if ! run_script "$BOOTUPD_SCRIPT" set-default "$rebased_image_name"; then
log_error "Failed to deploy rebased image via bootloader."
exit 1
fi
log_success "Image '$rebased_image_name' deployed as default boot entry."
# Commit the transaction
commit_transaction
log_orchestrator "System rebased to '$rebased_image_name'. A reboot is recommended."
}
# Rolls back the system to a previous deployment
rollback_system() {
local target_image_name="$1" # Optional: specific image to rollback to
log_orchestrator "Starting system rollback operation."
start_transaction "rollback_system" "${target_image_name:-last_good}"
update_transaction_phase "identify_target"
log_orchestrator "Identifying rollback target..."
# TODO: bootupd-alternative.sh needs a 'list-deployments' or 'get-previous' command
# to identify the previous bootable image or a specific target image.
local rollback_target_id
if [[ -n "$target_image_name" ]]; then
rollback_target_id="$target_image_name"
if ! "$COMPOSEFS_SCRIPT" list-images | grep -q "$rollback_target_id"; then
log_error "Rollback target image '$rollback_target_id' not found."
exit 1
fi
else
# Simulate getting previous good image from bootupd-alternative.sh
log_warning "No specific target provided. Simulating rollback to previous image."
# This would be a call to bootupd-alternative.sh to get the *previous* boot entry
# For now, we'll just pick a dummy previous one.
rollback_target_id="particle-os-dummy-previous-image" # Placeholder
log_info "Identified previous image for rollback: $rollback_target_id"
fi
update_transaction_phase "deploy_rollback"
log_orchestrator "Setting bootloader default to '$rollback_target_id'..."
if ! run_script "$BOOTUPD_SCRIPT" set-default "$rollback_target_id"; then
log_error "Failed to set bootloader default for rollback."
exit 1
fi
log_success "Bootloader default set to '$rollback_target_id'."
# Commit the transaction
commit_transaction
log_orchestrator "System rollback initiated. A reboot is required to complete."
}
# Shows the current status of the Particle-OS system
show_status() {
log_orchestrator "Gathering Particle-OS system status..."
echo "--- Particle-OS System Status ---"
echo
echo "1. Orchestrator Transaction Status:"
if [[ -f "$TRANSACTION_STATE" ]]; then
echo " Active: Yes"
cat "$TRANSACTION_STATE" | sed 's/^/ /'
else
echo " Active: No"
fi
echo
echo "2. ComposeFS Images Status:"
run_script "$COMPOSEFS_SCRIPT" list-images
echo
echo "3. ComposeFS Mounts Status:"
run_script "$COMPOSEFS_SCRIPT" list-mounts
echo
echo "4. ComposeFS Layers Status:"
run_script "$COMPOSEFS_SCRIPT" list-layers
echo
echo "5. Bootloader Status (via bootupd-alternative.sh):"
run_script "$BOOTUPD_SCRIPT" status
echo
echo "6. File Integrity Status (via fsverity-alternative.sh):"
run_script "$FSVERITY_SCRIPT" status
echo
echo "7. Live Overlay Status (via apt-layer.sh):"
# TODO: apt-layer.sh needs a 'status' command for live overlay
run_script "$APT_LAYER_SCRIPT" --live-overlay status
echo
log_orchestrator "Status report complete."
}
# --- Main Command Dispatch ---
main() {
check_root
init_workspace
check_script_dependencies
# Check for incomplete transactions on startup
check_incomplete_transactions
local command="${1:-help}"
shift || true # Shift arguments, allow no arguments for 'help'
case "$command" in
"install")
install_packages "$@"
;;
"install-dpkg")
# Enhanced installation with dpkg support
local base_image="$1"
shift
install_packages_enhanced "$base_image" "true" "$@"
;;
"rebase")
rebase_system "$@"
;;
"rollback")
rollback_system "$@"
;;
"status")
show_status
;;
"help"|"-h"|"--help")
cat << EOF
Particle-OS System Orchestrator
This script orchestrates the Particle-OS alternative tools to provide an atomic, immutable
Ubuntu experience similar to Fedora Silverblue/Kinoite.
Available Desktop Images:
- Particle-OS Corona (KDE Plasma): A radiant and expansive desktop experience.
- Particle-OS Apex (GNOME): A nimble, powerful, and adaptable desktop for power users.
- Particle-OS Base: Minimal base system for custom builds.
Usage: $0 <command> [options]
Commands:
install <base_image_name> <package1> [package2]... Install packages and create new system image
install-dpkg <base_image_name> <package1> [package2]... Install packages using dpkg (faster, more controlled)
rebase <new_base_image_name> Rebase the system to a new base image
rollback [target_image_name] Rollback to a previous system deployment
status Show comprehensive Particle-OS system status
help Show this help message
Examples:
$0 install particle-os-base-24.04 firefox steam # Install packages on a base image
$0 install-dpkg particle-os-base-24.04 firefox steam # Install packages using dpkg (optimized)
$0 rebase particle-os-base-25.04 # Rebase to a new Particle-OS base
$0 rollback # Rollback to the previous deployment
$0 status # Show current system status
Desktop Variants:
particle-os-corona-24.04 # KDE Plasma desktop
particle-os-apex-24.04 # GNOME desktop
particle-os-base-24.04 # Minimal base system
EOF
;;
*)
log_error "Unknown command: $command"
echo "Run '$0 help' for usage."
exit 1
;;
esac
}
# Run main function with all arguments
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi