- 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
824 lines
No EOL
32 KiB
Bash
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 |