#!/bin/bash ################################################################################################################ # # # WARNING: This file is automatically generated # # DO NOT modify this file directly as it will be overwritten # # # # Ubuntu uBlue BootC Alternative # # Generated on: 2025-07-13 00:44:28 # # # ################################################################################################################ set -euo pipefail # Ubuntu uBlue BootC Alternative - Self-contained version # This script contains all components merged into a single file # Based on actual bootc source code and documentation from https://github.com/bootc-dev/bootc # Version: 25.07.13 # Ubuntu uBlue BootC Alternative # Container-native bootable image system for Ubuntu # Source Ubuntu uBlue configuration (if available) if [[ -f "/usr/local/etc/particle-config.sh" ]]; then source "/usr/local/etc/particle-config.sh" info "Loaded Particle-OS configuration" else # Fallback logging functions if particle-config.sh not found info() { echo "[INFO] $1" >&2; } warning() { echo "[WARNING] $1" >&2; } error_exit() { echo "[ERROR] $1" >&2; exit 1; } success() { echo "[SUCCESS] $1" >&2; } warning "Ubuntu uBlue configuration not found, using defaults" fi # ============================================================================ # Header and Shared Functions # ============================================================================ # Utility functions for Particle-OS BootC Tool # These functions provide system introspection and core utilities # Fallback logging functions (in case particle-config.sh is not available) if ! declare -F log_info >/dev/null 2>&1; then log_info() { local message="$1" local script_name="${2:-bootc}" echo "[INFO] $message" } fi if ! declare -F log_warning >/dev/null 2>&1; then log_warning() { local message="$1" local script_name="${2:-bootc}" echo "[WARNING] $message" } fi if ! declare -F log_error >/dev/null 2>&1; then log_error() { local message="$1" local script_name="${2:-bootc}" echo "[ERROR] $message" >&2 } fi if ! declare -F log_success >/dev/null 2>&1; then log_success() { local message="$1" local script_name="${2:-bootc}" echo "[SUCCESS] $message" } fi if ! declare -F log_debug >/dev/null 2>&1; then log_debug() { local message="$1" local script_name="${2:-bootc}" echo "[DEBUG] $message" } fi # Check if running as root check_root() { if [[ $EUID -ne 0 ]]; then log_error "This script must be run as root" "bootc" exit 1 fi } # Require root privileges for specific operations require_root() { local operation="${1:-this operation}" if [[ $EUID -ne 0 ]]; then log_error "Root privileges required for: $operation" "bootc" log_info "Please run with sudo" "bootc" exit 1 fi } # Validate arguments validate_args() { local min_args="$1" local max_args="${2:-$min_args}" local usage_message="${3:-}" if [[ $# -lt $((min_args + 3)) ]] || [[ $# -gt $((max_args + 3)) ]]; then log_error "Invalid number of arguments" "bootc" if [[ -n "$usage_message" ]]; then echo "$usage_message" fi exit 1 fi } # Validate path validate_path() { local path="$1" local type="$2" # Check for null or empty paths if [[ -z "$path" ]]; then log_error "Empty $type path provided" "bootc" exit 1 fi # Check for path traversal attempts if [[ "$path" =~ \.\. ]]; then log_error "Path traversal attempt detected in $type: $path" "bootc" exit 1 fi # Check for absolute paths only (for source directories and mount points) if [[ "$type" == "source_dir" || "$type" == "mount_point" ]]; then if [[ ! "$path" =~ ^/ ]]; then log_error "$type must be an absolute path: $path" "bootc" exit 1 fi fi # Validate characters (alphanumeric, hyphens, underscores, slashes, dots) if [[ ! "$path" =~ ^[a-zA-Z0-9/._-]+$ ]]; then log_error "Invalid characters in $type: $path" "bootc" exit 1 fi echo "$path" } # Validate image name validate_image_name() { local name="$1" if [[ -z "$name" ]]; then log_error "Empty image name provided" "bootc" exit 1 fi if [[ ! "$name" =~ ^[a-zA-Z0-9/_-]+$ ]]; then log_error "Invalid image name: $name (only alphanumeric, hyphens, underscores, and slashes allowed)" "bootc" exit 1 fi echo "$name" } # Initialize directories init_directories() { log_info "Initializing BootC directories..." "bootc" # Create main directories local dirs=( "/var/lib/particle-os/bootc" "/var/log/particle-os" "/var/cache/particle-os" "/boot/loader/entries" ) for dir in "${dirs[@]}"; do if ! mkdir -p "$dir" 2>/dev/null; then log_warning "Failed to create directory $dir, attempting with sudo..." "bootc" if ! sudo mkdir -p "$dir" 2>/dev/null; then log_error "Failed to create directory: $dir" "bootc" return 1 fi fi # Set proper permissions if [[ -d "$dir" ]]; then sudo chown root:root "$dir" 2>/dev/null || true sudo chmod 755 "$dir" 2>/dev/null || true fi done log_success "BootC directories initialized" "bootc" return 0 } # Check dependencies check_dependencies() { log_info "Checking BootC dependencies..." "bootc" local dependencies=( "skopeo" "mksquashfs" "unsquashfs" "jq" "coreutils" ) local missing_deps=() for dep in "${dependencies[@]}"; do if ! command -v "$dep" >/dev/null 2>&1; then missing_deps+=("$dep") fi done if [[ ${#missing_deps[@]} -gt 0 ]]; then log_error "Missing dependencies: ${missing_deps[*]}" "bootc" log_info "Install with: sudo apt install squashfs-tools skopeo jq" "bootc" return 1 fi log_success "All dependencies available" "bootc" return 0 } # Global variables BOOTC_DIR="/var/lib/particle-os/bootc" BOOTC_LOG="/var/log/particle-os/bootc.log" BOOTC_CACHE="/var/cache/particle-os" BOOT_ENTRIES_DIR="/boot/loader/entries" # Cleanup function cleanup() { local exit_code=$? # Clean up any temporary files or mounts if [[ -n "${TEMP_MOUNT:-}" ]] && [[ -d "$TEMP_MOUNT" ]]; then log_info "Cleaning up temporary mount: $TEMP_MOUNT" "bootc" umount "$TEMP_MOUNT" 2>/dev/null || true rmdir "$TEMP_MOUNT" 2>/dev/null || true fi exit $exit_code } # Set up trap for cleanup trap cleanup EXIT INT TERM # Show version information show_version() { cat << EOF bootc-alternative.sh - Particle-OS alternative to bootc Version: 25.01.27 Compiled: $(date '+%Y-%m-%d %H:%M:%S UTC' -d @$(stat -c %Y "$0" 2>/dev/null || echo $(date +%s))) Features: - Container image validation and deployment - OSTree integration with ComposeFS backend - Bazzite-style status output with deployment tracking - Kernel arguments management - Registry authentication and secrets management - Transient overlay support for /usr - Bootloader management (UEFI, GRUB, LILO, syslinux) - System reinstallation and rollback capabilities - Systemd integration and service management Based on bootc-dev/bootc with Particle-OS enhancements EOF } # --- END OF SCRIPTLET: 00-header.sh --- # ============================================================================ # Dependency Checking and Validation # ============================================================================ # Check dependencies for Ubuntu uBlue BootC Alternative check_dependencies() { local missing_packages=() # Core dependencies for bootc-alternative.sh local required_packages=( "podman" # Container operations "skopeo" # Container image inspection "jq" # JSON processing "mksquashfs" # ComposeFS image creation "unsquashfs" # ComposeFS image extraction "mount" # Filesystem mounting "umount" # Filesystem unmounting "chroot" # Container operations "rsync" # File synchronization "findmnt" # Mount point detection "stat" # File status "numfmt" # Human-readable numbers "bc" # Mathematical calculations ) # Optional dependencies (warn if missing) local optional_packages=( "lsof" # Process detection (for check_usr_processes) "gzip" # Compression support "yq" # TOML processing ) # Check required packages for pkg in "${required_packages[@]}"; do if ! command -v "$pkg" &> /dev/null; then missing_packages+=("$pkg") fi done # Check optional packages for pkg in "${optional_packages[@]}"; do if ! command -v "$pkg" &> /dev/null; then warning "Optional package not found: $pkg" fi done if [[ ${#missing_packages[@]} -gt 0 ]]; then error_exit "Missing required packages: ${missing_packages[*]}" fi info "All required dependencies are available" } # --- END OF SCRIPTLET: 02-dependencies.sh --- # ============================================================================ # Container Operations (Lint, Build, Deploy) # ============================================================================ # bootc container lint equivalent # Validates that a container image is suitable for booting # Based on actual bootc lints.rs implementation container_lint() { local image_name="$1" local exit_code=0 info "Validating container image for bootability: $image_name" info "Using bootc image requirements from: https://bootc-dev.github.io/bootc/bootc-images.html" # Check if image exists if ! podman image exists "$image_name"; then error_exit "Container image $image_name does not exist" fi # Check for bootc metadata label (strongly recommended) local bootc_label=$(podman inspect "$image_name" --format='{{.Labels.containers.bootc}}' 2>/dev/null || echo "") if [[ "$bootc_label" == "1" ]]; then success "✓ bootc compatible label found (containers.bootc=1)" else warning "✗ bootc compatible label missing (recommended: LABEL containers.bootc 1)" fi # Core bootc validation checks (based on actual bootc requirements) local checks=( "systemd binary" "/usr/lib/systemd/systemd" "Root filesystem" "/" ) for ((i=0; i<${#checks[@]}; i+=2)); do local check_name="${checks[i]}" local check_path="${checks[i+1]}" if podman run --rm "$image_name" test -e "$check_path" 2>/dev/null; then success "✓ $check_name ($check_path) exists" else warning "✗ $check_name ($check_path) missing" exit_code=1 fi done # Kernel validation (bootc requirement: /usr/lib/modules/$kver/vmlinuz) if podman run --rm "$image_name" test -d "/usr/lib/modules" 2>/dev/null; then local kernel_modules=$(podman run --rm "$image_name" ls /usr/lib/modules 2>/dev/null | wc -l) if [[ $kernel_modules -gt 0 ]]; then success "✓ Kernel modules directory exists with $kernel_modules entries" # Check for kernel version and vmlinuz local kernel_version=$(podman run --rm "$image_name" ls /usr/lib/modules 2>/dev/null | head -1) if [[ -n "$kernel_version" ]]; then info "Kernel version: $kernel_version" # Check for vmlinuz in kernel directory (bootc requirement) if podman run --rm "$image_name" test -f "/usr/lib/modules/$kernel_version/vmlinuz" 2>/dev/null; then success "✓ Kernel binary found: /usr/lib/modules/$kernel_version/vmlinuz" else warning "✗ Kernel binary missing: /usr/lib/modules/$kernel_version/vmlinuz" exit_code=1 fi # Check for initramfs (bootc requirement: initramfs.img in kernel directory) if podman run --rm "$image_name" test -f "/usr/lib/modules/$kernel_version/initramfs.img" 2>/dev/null; then success "✓ Initramfs found: /usr/lib/modules/$kernel_version/initramfs.img" else warning "✗ Initramfs missing: /usr/lib/modules/$kernel_version/initramfs.img" exit_code=1 fi fi else warning "✗ No kernel modules found" exit_code=1 fi else warning "✗ Kernel modules directory missing (/usr/lib/modules)" exit_code=1 fi # Check that /boot should NOT contain content (bootc requirement) local boot_content=$(podman run --rm "$image_name" find /boot -type f 2>/dev/null | wc -l) if [[ $boot_content -eq 0 ]]; then success "✓ /boot directory is empty (bootc requirement)" else warning "✗ /boot directory contains $boot_content files (bootc recommends empty /boot)" info "bootc will copy kernel/initramfs from /usr/lib/modules to /boot as needed" fi # Check that systemd is present and executable if podman run --rm "$image_name" test -x "/usr/lib/systemd/systemd" 2>/dev/null; then success "✓ systemd binary is executable" else warning "✗ systemd binary is not executable" exit_code=1 fi # Check for OSTree integration (optional as of bootc 1.1.3+) if podman run --rm "$image_name" test -d "/etc/ostree" 2>/dev/null; then success "✓ OSTree configuration exists (optional for bootc 1.1.3+)" else info "ℹ OSTree configuration not found (optional for bootc 1.1.3+)" fi # Check for composefs support (strongly recommended) if podman run --rm "$image_name" test -f "/etc/ostree/ostree.conf" 2>/dev/null; then local composefs_enabled=$(podman run --rm "$image_name" grep -q "composefs" /etc/ostree/ostree.conf 2>/dev/null && echo "enabled" || echo "disabled") if [[ "$composefs_enabled" == "enabled" ]]; then success "✓ composefs backend enabled (recommended)" else warning "✗ composefs backend not enabled (strongly recommended)" fi else info "ℹ OSTree configuration file not found (composefs check skipped)" fi # Check for kernel arguments configuration (bootc feature) if podman run --rm "$image_name" test -d "/usr/lib/bootc/kargs.d" 2>/dev/null; then local kargs_files=$(podman run --rm "$image_name" find /usr/lib/bootc/kargs.d -name "*.toml" 2>/dev/null | wc -l) if [[ $kargs_files -gt 0 ]]; then success "✓ Kernel arguments configuration found: $kargs_files files" else info "ℹ Kernel arguments directory exists but no .toml files found" fi else info "ℹ Kernel arguments directory not found (/usr/lib/bootc/kargs.d)" fi # Check for authentication configuration (secrets management) local auth_locations=("/etc/ostree/auth.json" "/run/ostree/auth.json" "/usr/lib/ostree/auth.json") local auth_found=false for auth_path in "${auth_locations[@]}"; do if podman run --rm "$image_name" test -f "$auth_path" 2>/dev/null; then success "✓ Authentication file found: $auth_path" auth_found=true break fi done if [[ "$auth_found" == "false" ]]; then info "ℹ No authentication files found (required for private registry access)" fi # Check for proper filesystem structure (bootc guidance) if podman run --rm "$image_name" test -d "/usr" 2>/dev/null; then success "✓ /usr directory exists (for read-only data and executables)" fi if podman run --rm "$image_name" test -d "/var" 2>/dev/null; then success "✓ /var directory exists (for writable data)" fi if [[ $exit_code -eq 0 ]]; then success "Container validation passed - image is suitable for booting" info "This image meets bootc compatibility requirements" else warning "Container validation failed - image may not be suitable for booting" info "Some requirements are missing or incorrect" fi return $exit_code } # bootc container build equivalent # Builds a bootable container image build_container() { local dockerfile="$1" local image_name="$2" local tag="${3:-latest}" if [[ ! -f "$dockerfile" ]]; then error_exit "Dockerfile not found: $dockerfile" fi info "Building bootable container image: $image_name:$tag" info "Using Dockerfile: $dockerfile" info "Following bootc image requirements: https://bootc-dev.github.io/bootc/bootc-images.html" info "Following bootc building guidance: https://bootc-dev.github.io/bootc/building/guidance.html" # Apply pending kernel arguments if any local pending_kargs_file="$KARGS_DIR/pending.toml" if [[ -f "$pending_kargs_file" ]]; then info "Applying pending kernel arguments to build" # Copy kernel arguments to build context cp "$pending_kargs_file" ./kargs.toml info "Kernel arguments will be applied during deployment" fi # Build the container image using podman if podman build -f "$dockerfile" -t "$image_name:$tag" .; then success "Container image built successfully" # Validate the built image using bootc-style validation info "Validating built image for bootability..." container_lint "$image_name:$tag" else error_exit "Container build failed" fi } # bootc container deploy equivalent # Deploys a container image as a transactional, in-place OS update using ComposeFS backend deploy_container() { local image_name="$1" local tag="${2:-latest}" local full_image="$image_name:$tag" info "Deploying container image as transactional OS update: $full_image" info "Using ComposeFS backend for advanced layering and deduplication" # Validate the container first (bootc requirement) if ! container_lint "$full_image"; then error_exit "Container validation failed - cannot deploy non-bootable image" fi # Check if composefs-alternative.sh is available local composefs_script="" for path in "/usr/local/bin/composefs-alternative.sh" "/usr/bin/composefs-alternative.sh" "./composefs-alternative.sh"; do if [[ -x "$path" ]]; then composefs_script="$path" break fi done if [[ -z "$composefs_script" ]]; then warning "composefs-alternative.sh not found, falling back to direct ostree container commit" deploy_container_traditional "$image_name" "$tag" return $? fi info "Using ComposeFS backend: $composefs_script" # Create a new ostree deployment from the container using ComposeFS local deployment_name="bootc-$(date +%Y%m%d-%H%M%S)" local temp_dir="/tmp/bootc-deploy-$$" local composefs_image="$temp_dir/composefs-image" info "Creating transactional deployment: $deployment_name" info "Using ComposeFS for advanced image management" # Create temporary directory mkdir -p "$temp_dir" # Step 1: Pull the container image if not already present info "Step 1: Pulling container image" if ! podman pull "$full_image" --quiet; then error_exit "Failed to pull container image: $full_image" fi # Step 2: Export container rootfs to temporary directory info "Step 2: Exporting container rootfs" local container_id=$(podman create "$full_image" /bin/true 2>/dev/null) if [[ -z "$container_id" ]]; then error_exit "Failed to create temporary container" fi if ! podman export "$container_id" | tar -x -C "$temp_dir"; then podman rm "$container_id" 2>/dev/null || true error_exit "Failed to export container rootfs" fi # Clean up temporary container podman rm "$container_id" 2>/dev/null || true # Step 3: Create ComposeFS image from rootfs info "Step 3: Creating ComposeFS image" if ! "$composefs_script" create "$temp_dir" "$composefs_image"; then rm -rf "$temp_dir" error_exit "Failed to create ComposeFS image" fi # Step 4: Commit ComposeFS image to OSTree info "Step 4: Committing ComposeFS image to OSTree" local ostree_commit_success=false # Try direct ComposeFS integration if available if ostree --version | grep -q "composefs"; then info "OSTree supports ComposeFS, using direct integration" if ostree commit --repo="$OSTREE_REPO" --tree=composefs:"$composefs_image" --branch="$deployment_name" --subject="bootc deployment: $full_image"; then ostree_commit_success=true fi fi # Fallback: mount ComposeFS and commit as regular tree if [[ "$ostree_commit_success" == "false" ]]; then info "Using ComposeFS mount fallback for OSTree integration" local composefs_mount="$temp_dir/composefs-mount" mkdir -p "$composefs_mount" if "$composefs_script" mount "$composefs_image" "$composefs_mount"; then if ostree commit --repo="$OSTREE_REPO" --tree=dir:"$composefs_mount" --branch="$deployment_name" --subject="bootc deployment: $full_image"; then ostree_commit_success=true fi # Unmount ComposeFS "$composefs_script" unmount "$composefs_mount" 2>/dev/null || true fi fi # Clean up temporary files rm -rf "$temp_dir" if [[ "$ostree_commit_success" == "false" ]]; then error_exit "Failed to commit ComposeFS image to OSTree" fi # Step 5: Deploy the new OSTree commit info "Step 5: Deploying new OSTree commit" if ostree admin deploy "$deployment_name"; then success "✓ Container deployed successfully as $deployment_name using ComposeFS backend" info "✓ This is a transactional, in-place OS update with advanced layering" info "✓ ComposeFS provides deduplication and efficient storage" info "✓ Reboot to activate the new deployment" # Clear pending kernel arguments after successful deployment if [[ -f "$KARGS_DIR/pending.toml" ]]; then rm "$KARGS_DIR/pending.toml" info "Pending kernel arguments cleared after deployment" fi # Show deployment status echo -e "\n=== Deployment Status ===" ostree admin status # Show ComposeFS benefits echo -e "\n=== ComposeFS Benefits ===" info "✓ Advanced layering for efficient updates" info "✓ Content-addressable storage for deduplication" info "✓ Optimized boot times with lazy loading" info "✓ Reduced storage footprint through sharing" else error_exit "Failed to deploy OSTree commit" fi } # Traditional deployment fallback (original implementation) deploy_container_traditional() { local image_name="$1" local tag="${2:-latest}" local full_image="$image_name:$tag" info "Using traditional deployment method (direct ostree container commit)" # Create a new ostree deployment from the container local deployment_name="bootc-$(date +%Y%m%d-%H%M%S)" info "Creating transactional deployment: $deployment_name" # Export container to ostree (this is the transactional update) if ostree container commit "$full_image" "$deployment_name"; then success "Container deployed successfully as $deployment_name" info "This is a transactional, in-place OS update" info "Reboot to activate the new deployment" # Clear pending kernel arguments after successful deployment if [[ -f "$KARGS_DIR/pending.toml" ]]; then rm "$KARGS_DIR/pending.toml" info "Pending kernel arguments cleared after deployment" fi # Show deployment status echo -e "\n=== Deployment Status ===" ostree admin status else error_exit "Container deployment failed" fi } # bootc container list equivalent # Lists available bootable container deployments list_deployments() { info "Listing available bootable container deployments" echo "=== OSTree Deployments ===" ostree admin status echo -e "\n=== Container Images ===" podman images | grep -E "(ublue|bootc)" || warning "No ublue/bootc container images found" echo -e "\n=== Available Rollback Points ===" ostree log --repo="$OSTREE_REPO" $(ostree admin status | grep '^*' | awk '{print $2}') } # bootc container rollback equivalent # Rolls back to previous deployment (transactional rollback) rollback_deployment() { info "Performing transactional rollback" # Get current deployment status local status_output=$(ostree admin status) local deployments=($(echo "$status_output" | grep -v '^[[:space:]]*$' | awk '{print $2}')) if [[ ${#deployments[@]} -lt 2 ]]; then error_exit "No previous deployment available for rollback" fi # Find current and previous deployments local current_deployment="" local previous_deployment="" # Parse ostree admin status output while IFS= read -r line; do if [[ "$line" =~ ^\*[[:space:]]+([^[:space:]]+) ]]; then current_deployment="${BASH_REMATCH[1]}" elif [[ "$line" =~ ^[[:space:]]+([^[:space:]]+) ]] && [[ -z "$previous_deployment" ]]; then previous_deployment="${BASH_REMATCH[1]}" fi done <<< "$status_output" if [[ -z "$previous_deployment" ]]; then error_exit "No previous deployment found" fi info "Rolling back from $current_deployment to $previous_deployment" info "This is a transactional rollback operation" if ostree admin rollback; then success "Transactional rollback completed successfully" info "Reboot to activate the rollback" # Show new deployment status echo -e "\n=== New Deployment Status ===" ostree admin status else error_exit "Rollback failed" fi } # bootc container upgrade equivalent # Checks for and applies available container updates check_updates() { local image_name="$1" local tag="${2:-latest}" info "Checking for container updates: $image_name:$tag" # Get local image digest local local_digest=$(podman image inspect "$image_name:$tag" --format='{{.Digest}}' 2>/dev/null || echo "") if [[ -z "$local_digest" ]]; then warning "Local image not found, pulling to check for updates" if podman pull "$image_name:$tag" --quiet; then success "Image pulled successfully" else error_exit "Failed to pull image" fi return 0 fi # Get remote image digest using skopeo info "Comparing local and remote image digests..." local remote_digest=$(skopeo inspect --no-tls-verify "docker://$image_name:$tag" 2>/dev/null | jq -r '.Digest' 2>/dev/null || echo "") if [[ -z "$remote_digest" ]]; then warning "Could not fetch remote digest, attempting pull to check for updates" if podman pull "$image_name:$tag" --quiet; then local new_local_digest=$(podman image inspect "$image_name:$tag" --format='{{.Digest}}' 2>/dev/null || echo "") if [[ "$new_local_digest" != "$local_digest" ]]; then success "Newer version of container image available" info "Local digest: $local_digest" info "New digest: $new_local_digest" else info "No updates available for $image_name:$tag" fi else info "No updates available for $image_name:$tag" fi else if [[ "$remote_digest" != "$local_digest" ]]; then success "Newer version of container image available" info "Local digest: $local_digest" info "Remote digest: $remote_digest" info "Use 'deploy' command to apply the update" else info "No updates available for $image_name:$tag" fi fi } # --- END OF SCRIPTLET: 04-container.sh --- # ============================================================================ # ComposeFS Extension Operations # ============================================================================ # OSTree extension operations # ComposeFS/OSTree interoperability and advanced OSTree operations # OSTree container operations ostree_container_operations() { local action="$1" shift case "$action" in "commit") ostree_container_commit "$@" ;; "pull") ostree_container_pull "$@" ;; "list") ostree_container_list "$@" ;; "diff") ostree_container_diff "$@" ;; "mount") ostree_container_mount "$@" ;; "unmount") ostree_container_unmount "$@" ;; *) error_exit "Unknown ostree container action: $action" ;; esac } # Create OSTree commit from container image ostree_container_commit() { local image_name="$1" local ref_name="${2:-latest}" if [[ -z "$image_name" ]]; then error_exit "Container image name required" fi info "Creating OSTree commit from container: $image_name" # Validate container first if ! container_lint "$image_name"; then error_exit "Container validation failed" fi # Create OSTree commit using ostree container commit if ostree container commit "$image_name" "$ref_name"; then success "OSTree commit created successfully: $ref_name" info "Commit hash: $(ostree rev-parse "$ref_name")" else error_exit "Failed to create OSTree commit" fi } # Pull container image to OSTree repository ostree_container_pull() { local image_name="$1" local ref_name="${2:-latest}" if [[ -z "$image_name" ]]; then error_exit "Container image name required" fi info "Pulling container to OSTree repository: $image_name" # Pull container using ostree container pull if ostree container pull "$image_name" "$ref_name"; then success "Container pulled successfully to OSTree repository" info "Available as ref: $ref_name" else error_exit "Failed to pull container to OSTree repository" fi } # List OSTree container references ostree_container_list() { info "Listing OSTree container references" echo "=== OSTree Container Refs ===" ostree refs --repo="$OSTREE_REPO" | grep "^container/" || info "No container references found" echo -e "\n=== OSTree Commits ===" ostree log --repo="$OSTREE_REPO" --oneline | head -10 } # Show diff between container and current deployment ostree_container_diff() { local image_name="$1" if [[ -z "$image_name" ]]; then error_exit "Container image name required" fi info "Showing diff between container and current deployment: $image_name" # Get current deployment local current_deployment=$(ostree admin status | grep '^*' | awk '{print $2}') if [[ -z "$current_deployment" ]]; then error_exit "No current deployment found" fi info "Current deployment: $current_deployment" # Create temporary commit for comparison local temp_ref="temp-$(date +%s)" if ostree container commit "$image_name" "$temp_ref"; then echo "=== Diff: $current_deployment -> $image_name ===" ostree diff "$current_deployment" "$temp_ref" || info "No differences found" # Clean up temporary ref ostree refs --repo="$OSTREE_REPO" --delete "$temp_ref" else error_exit "Failed to create temporary commit for diff" fi } # Mount OSTree deployment for inspection ostree_container_mount() { local ref_name="$1" local mount_point="${2:-/tmp/ostree-mount}" if [[ -z "$ref_name" ]]; then error_exit "OSTree reference name required" fi info "Mounting OSTree deployment: $ref_name at $mount_point" # Create mount point mkdir -p "$mount_point" # Mount OSTree deployment if ostree admin mount "$ref_name" "$mount_point"; then success "OSTree deployment mounted at: $mount_point" info "Use 'ostree admin unmount $mount_point' to unmount" else error_exit "Failed to mount OSTree deployment" fi } # Unmount OSTree deployment ostree_container_unmount() { local mount_point="$1" if [[ -z "$mount_point" ]]; then mount_point="/tmp/ostree-mount" fi info "Unmounting OSTree deployment from: $mount_point" if ostree admin unmount "$mount_point"; then success "OSTree deployment unmounted from: $mount_point" rmdir "$mount_point" 2>/dev/null || true else error_exit "Failed to unmount OSTree deployment" fi } # ComposeFS backend operations composefs_operations() { local action="$1" shift case "$action" in "enable") enable_composefs_backend ;; "disable") disable_composefs_backend ;; "status") check_composefs_status ;; "convert") convert_to_composefs "$@" ;; *) error_exit "Unknown composefs action: $action" ;; esac } # Enable ComposeFS backend for OSTree enable_composefs_backend() { info "Enabling ComposeFS backend for OSTree" # Check if composefs is available if ! command -v composefs &>/dev/null; then error_exit "composefs not available - install composefs package" fi # Check current OSTree configuration local ostree_conf="/etc/ostree/ostree.conf" if [[ -f "$ostree_conf" ]]; then if grep -q "composefs" "$ostree_conf"; then info "ComposeFS backend already configured" return 0 fi fi # Create OSTree configuration directory mkdir -p /etc/ostree # Add ComposeFS configuration cat >> "$ostree_conf" << EOF [core] composefs=true EOF success "ComposeFS backend enabled in OSTree configuration" info "New deployments will use ComposeFS backend" } # Disable ComposeFS backend for OSTree disable_composefs_backend() { info "Disabling ComposeFS backend for OSTree" local ostree_conf="/etc/ostree/ostree.conf" if [[ -f "$ostree_conf" ]]; then # Remove composefs configuration sed -i '/composefs=true/d' "$ostree_conf" success "ComposeFS backend disabled in OSTree configuration" else info "No OSTree configuration found" fi } # Check ComposeFS backend status check_composefs_status() { info "Checking ComposeFS backend status" echo "=== ComposeFS Backend Status ===" # Check if composefs binary is available if command -v composefs &>/dev/null; then success "✓ composefs binary available" composefs --version else warning "✗ composefs binary not found" fi # Check OSTree configuration local ostree_conf="/etc/ostree/ostree.conf" if [[ -f "$ostree_conf" ]]; then if grep -q "composefs=true" "$ostree_conf"; then success "✓ ComposeFS backend enabled in OSTree configuration" else info "ℹ ComposeFS backend not enabled in OSTree configuration" fi else info "ℹ No OSTree configuration file found" fi # Check current deployment local current_deployment=$(ostree admin status | grep '^*' | awk '{print $2}') if [[ -n "$current_deployment" ]]; then echo -e "\n=== Current Deployment ===" ostree admin status fi } # Convert existing deployment to use ComposeFS convert_to_composefs() { local ref_name="$1" if [[ -z "$ref_name" ]]; then error_exit "OSTree reference name required" fi info "Converting deployment to use ComposeFS: $ref_name" # Enable ComposeFS backend first enable_composefs_backend # Create new deployment with ComposeFS local new_ref="${ref_name}-composefs" if ostree commit --repo="$OSTREE_REPO" --branch="$new_ref" --tree=ref:"$ref_name"; then success "Deployment converted to ComposeFS: $new_ref" info "Use 'ostree admin deploy $new_ref' to activate" else error_exit "Failed to convert deployment to ComposeFS" fi } # OSTree repository management ostree_repo_operations() { local action="$1" shift case "$action" in "init") init_ostree_repo ;; "check") check_ostree_repo ;; "clean") clean_ostree_repo "$@" ;; "gc") garbage_collect_ostree_repo ;; *) error_exit "Unknown ostree repo action: $action" ;; esac } # Initialize OSTree repository init_ostree_repo() { info "Initializing OSTree repository" if [[ -d "$OSTREE_REPO" ]]; then info "OSTree repository already exists" return 0 fi if ostree admin init-fs "$OSTREE_REPO"; then success "OSTree repository initialized" else error_exit "Failed to initialize OSTree repository" fi } # Check OSTree repository health check_ostree_repo() { info "Checking OSTree repository health" if [[ ! -d "$OSTREE_REPO" ]]; then error_exit "OSTree repository not found: $OSTREE_REPO" fi echo "=== OSTree Repository Health ===" # Check repository integrity if ostree fsck --repo="$OSTREE_REPO"; then success "✓ Repository integrity check passed" else error_exit "Repository integrity check failed" fi # Show repository statistics echo -e "\n=== Repository Statistics ===" ostree summary --repo="$OSTREE_REPO" --view } # Clean OSTree repository clean_ostree_repo() { local keep_refs="${1:-10}" info "Cleaning OSTree repository (keeping $keep_refs references)" # Remove old deployments local deployments=($(ostree admin status | grep -v '^*' | awk '{print $2}' | tail -n +$((keep_refs + 1)))) for deployment in "${deployments[@]}"; do if [[ -n "$deployment" ]]; then info "Removing old deployment: $deployment" ostree admin undeploy "$deployment" || warning "Failed to remove deployment: $deployment" fi done success "OSTree repository cleanup completed" } # Garbage collect OSTree repository garbage_collect_ostree_repo() { info "Running garbage collection on OSTree repository" if ostree admin cleanup --repo="$OSTREE_REPO"; then success "Garbage collection completed" else error_exit "Garbage collection failed" fi } # Deployment tracking functions for Particle-OS # These functions manage deployment state similar to Bazzite's bootc status # Initialize deployment tracking init_deployment_tracking() { info "Initializing deployment tracking" # Create deployment tracking directory mkdir -p "$PARTICLE_WORKSPACE" # Initialize deployment files if they don't exist local current_deployment_file="$PARTICLE_WORKSPACE/current-deployment" local staged_deployment_file="$PARTICLE_WORKSPACE/staged-deployment" local rollback_deployment_file="$PARTICLE_WORKSPACE/rollback-deployment" if [[ ! -f "$current_deployment_file" ]]; then cat > "$current_deployment_file" << EOF { "image": "unknown", "digest": "unknown", "version": "unknown", "timestamp": "$(date -Iseconds)", "deployment_type": "initial" } EOF fi if [[ ! -f "$staged_deployment_file" ]]; then cat > "$staged_deployment_file" << EOF { "image": "none", "digest": "none", "version": "none", "timestamp": "none", "deployment_type": "none" } EOF fi if [[ ! -f "$rollback_deployment_file" ]]; then cat > "$rollback_deployment_file" << EOF { "image": "none", "digest": "none", "version": "none", "timestamp": "none", "deployment_type": "none" } EOF fi success "Deployment tracking initialized" } # Stage a new deployment stage_deployment() { local image="$1" local digest="$2" local version="$3" if [[ -z "$image" ]]; then error_exit "Image name required for staging" fi info "Staging deployment: $image" # Create staged deployment file local staged_deployment_file="$PARTICLE_WORKSPACE/staged-deployment" cat > "$staged_deployment_file" << EOF { "image": "$image", "digest": "${digest:-unknown}", "version": "${version:-$(date '+%y.%m.%d.%H%M')}", "timestamp": "$(date -Iseconds)", "deployment_type": "staged" } EOF success "Deployment staged: $image" info "Use 'deploy' to activate the staged deployment" } # Deploy staged deployment deploy_staged() { info "Deploying staged deployment" local staged_deployment_file="$PARTICLE_WORKSPACE/staged-deployment" local current_deployment_file="$PARTICLE_WORKSPACE/current-deployment" local rollback_deployment_file="$PARTICLE_WORKSPACE/rollback-deployment" if [[ ! -f "$staged_deployment_file" ]]; then error_exit "No staged deployment found" fi # Read staged deployment info local staged_image=$(jq -r '.image' "$staged_deployment_file") local staged_digest=$(jq -r '.digest' "$staged_deployment_file") local staged_version=$(jq -r '.version' "$staged_deployment_file") local staged_timestamp=$(jq -r '.timestamp' "$staged_deployment_file") if [[ "$staged_image" == "none" ]]; then error_exit "No valid staged deployment found" fi # Backup current deployment as rollback if [[ -f "$current_deployment_file" ]]; then cp "$current_deployment_file" "$rollback_deployment_file" info "Current deployment backed up as rollback" fi # Move staged to current cp "$staged_deployment_file" "$current_deployment_file" # Update current deployment type jq '.deployment_type = "current"' "$current_deployment_file" > "$current_deployment_file.tmp" && mv "$current_deployment_file.tmp" "$current_deployment_file" # Clear staged deployment cat > "$staged_deployment_file" << EOF { "image": "none", "digest": "none", "version": "none", "timestamp": "none", "deployment_type": "none" } EOF success "Deployment activated: $staged_image" info "Previous deployment available as rollback" } # Rollback to previous deployment rollback_deployment() { info "Rolling back to previous deployment" local current_deployment_file="$PARTICLE_WORKSPACE/current-deployment" local rollback_deployment_file="$PARTICLE_WORKSPACE/rollback-deployment" local staged_deployment_file="$PARTICLE_WORKSPACE/staged-deployment" if [[ ! -f "$rollback_deployment_file" ]]; then error_exit "No rollback deployment available" fi # Read rollback deployment info local rollback_image=$(jq -r '.image' "$rollback_deployment_file") if [[ "$rollback_image" == "none" ]]; then error_exit "No valid rollback deployment found" fi # Move current to staged (for potential re-deploy) if [[ -f "$current_deployment_file" ]]; then cp "$current_deployment_file" "$staged_deployment_file" jq '.deployment_type = "staged"' "$staged_deployment_file" > "$staged_deployment_file.tmp" && mv "$staged_deployment_file.tmp" "$staged_deployment_file" fi # Move rollback to current cp "$rollback_deployment_file" "$current_deployment_file" jq '.deployment_type = "current"' "$current_deployment_file" > "$current_deployment_file.tmp" && mv "$current_deployment_file.tmp" "$current_deployment_file" # Clear rollback cat > "$rollback_deployment_file" << EOF { "image": "none", "digest": "none", "version": "none", "timestamp": "none", "deployment_type": "none" } EOF success "Rolled back to: $rollback_image" info "Previous deployment staged for potential re-deploy" } # Get deployment information get_deployment_info() { local deployment_type="$1" case "$deployment_type" in "current") local file="$PARTICLE_WORKSPACE/current-deployment" ;; "staged") local file="$PARTICLE_WORKSPACE/staged-deployment" ;; "rollback") local file="$PARTICLE_WORKSPACE/rollback-deployment" ;; *) error_exit "Invalid deployment type: $deployment_type (use: current, staged, rollback)" ;; esac if [[ -f "$file" ]]; then cat "$file" else echo '{"image": "none", "digest": "none", "version": "none", "timestamp": "none", "deployment_type": "none"}' fi } # Update deployment from container image update_deployment_from_container() { local image="$1" local digest="$2" local version="$3" if [[ -z "$image" ]]; then error_exit "Container image name required" fi info "Updating deployment from container: $image" # Extract digest if not provided if [[ -z "$digest" ]]; then if command -v skopeo &>/dev/null; then digest=$(skopeo inspect "docker://$image" | jq -r '.Digest' 2>/dev/null || echo "unknown") else digest="unknown" fi fi # Generate version if not provided if [[ -z "$version" ]]; then version="$(date '+%y.%m.%d.%H%M')" fi # Stage the new deployment stage_deployment "$image" "$digest" "$version" success "Deployment updated and staged: $image" info "Use 'deploy' to activate the new deployment" } # --- END OF SCRIPTLET: 05-ostree.sh --- # ============================================================================ # Bootloader Management # ============================================================================ # Bootloader management functions # Comprehensive bootloader integration with UEFI, GRUB, LILO, and syslinux support # Bootloader operations bootloader_operations() { local action="$1" shift case "$action" in "install") install_bootloader "$@" ;; "update") update_bootloader "$@" ;; "backup") backup_bootloader "$@" ;; "restore") restore_bootloader "$@" ;; "status") check_bootloader_status "$@" ;; "list") list_boot_entries "$@" ;; "add-entry") add_boot_entry "$@" ;; "remove-entry") remove_boot_entry "$@" ;; "set-default") set_default_boot_entry "$@" ;; *) error_exit "Unknown bootloader action: $action" ;; esac } # Install bootloader install_bootloader() { local bootloader_type="${1:-auto}" local device="${2:-auto}" info "Installing bootloader: $bootloader_type" case "$bootloader_type" in "auto") detect_and_install_bootloader "$device" ;; "grub") install_grub_bootloader "$device" ;; "uefi") install_uefi_bootloader "$device" ;; "lilo") install_lilo_bootloader "$device" ;; "syslinux") install_syslinux_bootloader "$device" ;; *) error_exit "Unsupported bootloader type: $bootloader_type" ;; esac } # Detect and install appropriate bootloader detect_and_install_bootloader() { local device="${1:-auto}" info "Auto-detecting bootloader type" # Check for UEFI if [[ -d "/sys/firmware/efi" ]]; then success "✓ UEFI system detected" install_uefi_bootloader "$device" elif command -v grub-install &>/dev/null; then success "✓ GRUB available" install_grub_bootloader "$device" elif command -v lilo &>/dev/null; then success "✓ LILO available" install_lilo_bootloader "$device" elif command -v syslinux &>/dev/null; then success "✓ SYSLINUX available" install_syslinux_bootloader "$device" else error_exit "No supported bootloader found" fi } # Install GRUB bootloader install_grub_bootloader() { local device="${1:-auto}" info "Installing GRUB bootloader" if ! command -v grub-install &>/dev/null; then error_exit "grub-install not available" fi # Auto-detect device if not specified if [[ "$device" == "auto" ]]; then device=$(get_root_device) info "Auto-detected root device: $device" fi # Install GRUB if grub-install "$device"; then success "✓ GRUB installed successfully on $device" # Update GRUB configuration if command -v update-grub &>/dev/null; then info "Updating GRUB configuration" if update-grub; then success "✓ GRUB configuration updated" else warning "✗ Failed to update GRUB configuration" fi fi else error_exit "Failed to install GRUB on $device" fi } # Install UEFI bootloader install_uefi_bootloader() { local device="${1:-auto}" info "Installing UEFI bootloader" if ! command -v efibootmgr &>/dev/null; then error_exit "efibootmgr not available" fi # Find EFI partition local efi_partition=$(find_efi_partition) if [[ -z "$efi_partition" ]]; then error_exit "EFI partition not found" fi info "EFI partition: $efi_partition" # Mount EFI partition local efi_mount="/tmp/efi-mount" mkdir -p "$efi_mount" if mount "$efi_partition" "$efi_mount"; then success "✓ EFI partition mounted" # Install systemd-boot (preferred for UEFI) if command -v bootctl &>/dev/null; then info "Installing systemd-boot" if bootctl install --esp-path="$efi_mount"; then success "✓ systemd-boot installed" else warning "✗ Failed to install systemd-boot, trying GRUB" install_grub_uefi "$efi_mount" fi else install_grub_uefi "$efi_mount" fi # Unmount EFI partition umount "$efi_mount" rmdir "$efi_mount" else error_exit "Failed to mount EFI partition" fi } # Install GRUB for UEFI install_grub_uefi() { local efi_mount="$1" info "Installing GRUB for UEFI" if ! command -v grub-install &>/dev/null; then error_exit "grub-install not available" fi # Install GRUB to EFI partition if grub-install --target=x86_64-efi --efi-directory="$efi_mount" --bootloader-id=ubuntu-ublue; then success "✓ GRUB installed for UEFI" # Update GRUB configuration if command -v update-grub &>/dev/null; then info "Updating GRUB configuration" if update-grub; then success "✓ GRUB configuration updated" else warning "✗ Failed to update GRUB configuration" fi fi else error_exit "Failed to install GRUB for UEFI" fi } # Install LILO bootloader install_lilo_bootloader() { local device="${1:-auto}" info "Installing LILO bootloader" if ! command -v lilo &>/dev/null; then error_exit "lilo not available" fi # Auto-detect device if not specified if [[ "$device" == "auto" ]]; then device=$(get_root_device) info "Auto-detected root device: $device" fi # Install LILO if lilo; then success "✓ LILO installed successfully" else error_exit "Failed to install LILO" fi } # Install SYSLINUX bootloader install_syslinux_bootloader() { local device="${1:-auto}" info "Installing SYSLINUX bootloader" if ! command -v syslinux &>/dev/null; then error_exit "syslinux not available" fi # Auto-detect device if not specified if [[ "$device" == "auto" ]]; then device=$(get_root_device) info "Auto-detected root device: $device" fi # Install SYSLINUX if syslinux "$device"; then success "✓ SYSLINUX installed successfully on $device" else error_exit "Failed to install SYSLINUX on $device" fi } # Update bootloader update_bootloader() { local bootloader_type="${1:-auto}" info "Updating bootloader: $bootloader_type" case "$bootloader_type" in "auto") detect_and_update_bootloader ;; "grub") update_grub_configuration ;; "uefi") update_uefi_entries ;; "lilo") update_lilo_configuration ;; "syslinux") update_syslinux_configuration ;; *) error_exit "Unsupported bootloader type: $bootloader_type" ;; esac } # Detect and update appropriate bootloader detect_and_update_bootloader() { info "Auto-detecting bootloader for update" # Check for UEFI if [[ -d "/sys/firmware/efi" ]]; then success "✓ UEFI system detected" update_uefi_entries elif command -v update-grub &>/dev/null; then success "✓ GRUB detected" update_grub_configuration elif command -v lilo &>/dev/null; then success "✓ LILO detected" update_lilo_configuration elif command -v syslinux &>/dev/null; then success "✓ SYSLINUX detected" update_syslinux_configuration else error_exit "No supported bootloader found for update" fi } # Update GRUB configuration update_grub_configuration() { info "Updating GRUB configuration" if command -v update-grub &>/dev/null; then if update-grub; then success "✓ GRUB configuration updated" else error_exit "Failed to update GRUB configuration" fi else error_exit "update-grub not available" fi } # Update UEFI entries update_uefi_entries() { info "Updating UEFI boot entries" if ! command -v efibootmgr &>/dev/null; then error_exit "efibootmgr not available" fi # Find EFI partition local efi_partition=$(find_efi_partition) if [[ -z "$efi_partition" ]]; then error_exit "EFI partition not found" fi # Mount EFI partition local efi_mount="/tmp/efi-mount" mkdir -p "$efi_mount" if mount "$efi_partition" "$efi_mount"; then success "✓ EFI partition mounted" # Update systemd-boot if available if command -v bootctl &>/dev/null; then info "Updating systemd-boot" if bootctl update; then success "✓ systemd-boot updated" else warning "✗ Failed to update systemd-boot" fi fi # Update GRUB if available if command -v grub-install &>/dev/null; then info "Updating GRUB for UEFI" if grub-install --target=x86_64-efi --efi-directory="$efi_mount" --bootloader-id=ubuntu-ublue; then success "✓ GRUB updated for UEFI" else warning "✗ Failed to update GRUB for UEFI" fi fi # Unmount EFI partition umount "$efi_mount" rmdir "$efi_mount" else error_exit "Failed to mount EFI partition" fi } # Update LILO configuration update_lilo_configuration() { info "Updating LILO configuration" if command -v lilo &>/dev/null; then if lilo; then success "✓ LILO configuration updated" else error_exit "Failed to update LILO configuration" fi else error_exit "lilo not available" fi } # Update SYSLINUX configuration update_syslinux_configuration() { info "Updating SYSLINUX configuration" if command -v syslinux &>/dev/null; then local device=$(get_root_device) if syslinux "$device"; then success "✓ SYSLINUX configuration updated" else error_exit "Failed to update SYSLINUX configuration" fi else error_exit "syslinux not available" fi } # Backup bootloader configuration backup_bootloader() { local backup_dir="${1:-/var/backup/bootloader}" info "Backing up bootloader configuration to: $backup_dir" mkdir -p "$backup_dir" local timestamp=$(date +%Y%m%d-%H%M%S) local backup_file="$backup_dir/bootloader-backup-$timestamp.tar.gz" # Create backup archive local backup_files=() # GRUB files if [[ -d "/boot/grub" ]]; then backup_files+=("/boot/grub") fi # UEFI files if [[ -d "/sys/firmware/efi" ]]; then local efi_partition=$(find_efi_partition) if [[ -n "$efi_partition" ]]; then local efi_mount="/tmp/efi-backup" mkdir -p "$efi_mount" if mount "$efi_partition" "$efi_mount"; then backup_files+=("$efi_mount") # Note: Will unmount after backup fi fi fi # LILO files if [[ -f "/etc/lilo.conf" ]]; then backup_files+=("/etc/lilo.conf") fi # SYSLINUX files if [[ -d "/boot/syslinux" ]]; then backup_files+=("/boot/syslinux") fi # Create backup if [[ ${#backup_files[@]} -gt 0 ]]; then if tar -czf "$backup_file" "${backup_files[@]}"; then success "✓ Bootloader backup created: $backup_file" # Unmount EFI if mounted if [[ -d "/tmp/efi-backup" ]]; then umount "/tmp/efi-backup" 2>/dev/null || true rmdir "/tmp/efi-backup" 2>/dev/null || true fi else error_exit "Failed to create bootloader backup" fi else warning "No bootloader files found to backup" fi } # Restore bootloader configuration restore_bootloader() { local backup_file="$1" if [[ -z "$backup_file" ]]; then error_exit "Backup file required" fi if [[ ! -f "$backup_file" ]]; then error_exit "Backup file not found: $backup_file" fi info "Restoring bootloader configuration from: $backup_file" warning "This will overwrite current bootloader configuration" # Confirm restoration echo -n "Are you sure you want to restore bootloader configuration? (yes/no): " read -r confirmation if [[ "$confirmation" != "yes" ]]; then info "Bootloader restoration cancelled" return 0 fi # Extract backup local temp_dir="/tmp/bootloader-restore" mkdir -p "$temp_dir" if tar -xzf "$backup_file" -C "$temp_dir"; then success "✓ Backup extracted" # Restore files if [[ -d "$temp_dir/boot/grub" ]]; then info "Restoring GRUB configuration" cp -r "$temp_dir/boot/grub" /boot/ 2>/dev/null || warning "Failed to restore GRUB" fi if [[ -d "$temp_dir/etc/lilo.conf" ]]; then info "Restoring LILO configuration" cp "$temp_dir/etc/lilo.conf" /etc/ 2>/dev/null || warning "Failed to restore LILO" fi if [[ -d "$temp_dir/boot/syslinux" ]]; then info "Restoring SYSLINUX configuration" cp -r "$temp_dir/boot/syslinux" /boot/ 2>/dev/null || warning "Failed to restore SYSLINUX" fi # Clean up rm -rf "$temp_dir" success "✓ Bootloader configuration restored" info "Reboot to activate restored configuration" else error_exit "Failed to extract backup" fi } # Check bootloader status check_bootloader_status() { info "Checking bootloader status" echo "=== Bootloader Status ===" # Check UEFI if [[ -d "/sys/firmware/efi" ]]; then success "✓ UEFI system detected" if command -v efibootmgr &>/dev/null; then echo -e "\n=== UEFI Boot Entries ===" efibootmgr else warning "✗ efibootmgr not available" fi else info "ℹ Legacy BIOS system detected" fi # Check GRUB if command -v grub-install &>/dev/null; then success "✓ GRUB available" if [[ -f "/boot/grub/grub.cfg" ]]; then success "✓ GRUB configuration exists" else warning "✗ GRUB configuration missing" fi else info "ℹ GRUB not available" fi # Check LILO if command -v lilo &>/dev/null; then success "✓ LILO available" if [[ -f "/etc/lilo.conf" ]]; then success "✓ LILO configuration exists" else warning "✗ LILO configuration missing" fi else info "ℹ LILO not available" fi # Check SYSLINUX if command -v syslinux &>/dev/null; then success "✓ SYSLINUX available" if [[ -d "/boot/syslinux" ]]; then success "✓ SYSLINUX files exist" else warning "✗ SYSLINUX files missing" fi else info "ℹ SYSLINUX not available" fi } # List boot entries list_boot_entries() { info "Listing boot entries" if [[ -d "/sys/firmware/efi" ]]; then echo "=== UEFI Boot Entries ===" if command -v efibootmgr &>/dev/null; then efibootmgr else warning "efibootmgr not available" fi else echo "=== GRUB Boot Entries ===" if command -v grub-probe &>/dev/null; then grub-probe --target=partmap /boot else warning "grub-probe not available" fi fi } # Add boot entry add_boot_entry() { local title="$1" local kernel="$2" local initrd="$3" if [[ -z "$title" || -z "$kernel" ]]; then error_exit "Title and kernel required" fi info "Adding boot entry: $title" if [[ -d "/sys/firmware/efi" ]]; then add_uefi_boot_entry "$title" "$kernel" "$initrd" else add_grub_boot_entry "$title" "$kernel" "$initrd" fi } # Add UEFI boot entry add_uefi_boot_entry() { local title="$1" local kernel="$2" local initrd="$3" if ! command -v efibootmgr &>/dev/null; then error_exit "efibootmgr not available" fi # Find EFI partition local efi_partition=$(find_efi_partition) if [[ -z "$efi_partition" ]]; then error_exit "EFI partition not found" fi # Create boot entry local boot_args="" if [[ -n "$initrd" ]]; then boot_args="initrd=$initrd" fi if efibootmgr --create --disk "$efi_partition" --part 1 --label "$title" --loader "$kernel" --unicode "$boot_args"; then success "✓ UEFI boot entry added: $title" else error_exit "Failed to add UEFI boot entry" fi } # Add GRUB boot entry add_grub_boot_entry() { local title="$1" local kernel="$2" local initrd="$3" info "Adding GRUB boot entry: $title" warning "GRUB boot entry addition requires manual configuration" info "Please edit /etc/default/grub and run update-grub" } # Remove boot entry remove_boot_entry() { local entry_id="$1" if [[ -z "$entry_id" ]]; then error_exit "Boot entry ID required" fi info "Removing boot entry: $entry_id" if [[ -d "/sys/firmware/efi" ]]; then if command -v efibootmgr &>/dev/null; then if efibootmgr --bootnum "$entry_id" --delete-bootnum; then success "✓ UEFI boot entry removed: $entry_id" else error_exit "Failed to remove UEFI boot entry" fi else error_exit "efibootmgr not available" fi else warning "Boot entry removal requires manual GRUB configuration" fi } # Set default boot entry set_default_boot_entry() { local entry_id="$1" if [[ -z "$entry_id" ]]; then error_exit "Boot entry ID required" fi info "Setting default boot entry: $entry_id" if [[ -d "/sys/firmware/efi" ]]; then if command -v efibootmgr &>/dev/null; then if efibootmgr --bootnum "$entry_id" --bootorder "$entry_id"; then success "✓ Default UEFI boot entry set: $entry_id" else error_exit "Failed to set default UEFI boot entry" fi else error_exit "efibootmgr not available" fi else warning "Default boot entry setting requires manual GRUB configuration" fi } # Helper functions get_root_device() { local root_device=$(findmnt -n -o SOURCE /) echo "$root_device" } find_efi_partition() { local efi_partition="" # Try to find EFI partition if command -v findmnt &>/dev/null; then efi_partition=$(findmnt -n -o SOURCE /boot/efi 2>/dev/null || echo "") fi # Fallback: look for EFI partition by label if [[ -z "$efi_partition" ]]; then efi_partition=$(blkid -L EFI 2>/dev/null || echo "") fi # Fallback: look for EFI partition by filesystem type if [[ -z "$efi_partition" ]]; then efi_partition=$(blkid -t TYPE=vfat | grep -o '/dev/[^:]*' | head -1 2>/dev/null || echo "") fi echo "$efi_partition" } # --- END OF SCRIPTLET: 06-bootloader.sh --- # ============================================================================ # System Reinstallation # ============================================================================ # System reinstallation functions # Complete system reinstallation with backup and validation # System reinstall operations system_reinstall_operations() { local action="$1" shift case "$action" in "prepare") prepare_reinstall "$@" ;; "execute") execute_reinstall "$@" ;; "backup") backup_system "$@" ;; "restore") restore_system "$@" ;; "validate") validate_reinstall "$@" ;; "rollback") rollback_reinstall "$@" ;; *) error_exit "Unknown reinstall action: $action" ;; esac } # Prepare system for reinstallation prepare_reinstall() { local image_name="$1" local backup_dir="${2:-/var/backup/bootc-reinstall}" if [[ -z "$image_name" ]]; then error_exit "Container image name required for reinstallation" fi info "Preparing system for reinstallation with: $image_name" # Validate container image if ! container_lint "$image_name"; then error_exit "Container validation failed - cannot reinstall with invalid image" fi # Create backup directory mkdir -p "$backup_dir" # Create backup of current system info "Creating backup of current system" backup_system "$backup_dir" # Validate disk space for reinstallation local required_space=$(podman image inspect "$image_name" --format='{{.Size}}' 2>/dev/null || echo "1073741824") # Default 1GB local available_space=$(df / | awk 'NR==2 {print $4}') if [[ $available_space -lt $required_space ]]; then error_exit "Insufficient disk space for reinstallation. Required: $((required_space / 1024 / 1024))MB, Available: $((available_space / 1024))MB" fi # Create reinstallation plan local plan_file="$backup_dir/reinstall-plan.json" cat > "$plan_file" << EOF { "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "image": "$image_name", "backup_dir": "$backup_dir", "current_deployment": "$(ostree admin status | grep '^*' | awk '{print $2}')", "system_info": { "hostname": "$(hostname)", "kernel": "$(uname -r)", "architecture": "$(uname -m)" } } EOF success "System prepared for reinstallation" info "Backup created at: $backup_dir" info "Reinstallation plan: $plan_file" info "Use 'execute' action to proceed with reinstallation" } # Execute system reinstallation execute_reinstall() { local image_name="$1" local backup_dir="${2:-/var/backup/bootc-reinstall}" local plan_file="$backup_dir/reinstall-plan.json" if [[ -z "$image_name" ]]; then error_exit "Container image name required for reinstallation" fi if [[ ! -f "$plan_file" ]]; then error_exit "Reinstallation plan not found. Run 'prepare' action first" fi info "Executing system reinstallation with: $image_name" warning "This operation will replace the current system deployment" # Confirm reinstallation echo -n "Are you sure you want to proceed with reinstallation? (yes/no): " read -r confirmation if [[ "$confirmation" != "yes" ]]; then info "Reinstallation cancelled" return 0 fi # Create new deployment from container local new_deployment="reinstall-$(date +%Y%m%d-%H%M%S)" info "Creating new deployment: $new_deployment" if ostree container commit "$image_name" "$new_deployment"; then success "New deployment created: $new_deployment" # Deploy the new system info "Deploying new system..." if ostree admin deploy "$new_deployment"; then success "System reinstallation completed successfully" info "New deployment: $new_deployment" info "Reboot to activate the new system" # Update reinstallation plan jq ".new_deployment = \"$new_deployment\" | .status = \"completed\"" "$plan_file" > "$plan_file.tmp" && mv "$plan_file.tmp" "$plan_file" # Show deployment status echo -e "\n=== New Deployment Status ===" ostree admin status else error_exit "Failed to deploy new system" fi else error_exit "Failed to create new deployment from container" fi } # Backup current system backup_system() { local backup_dir="${1:-/var/backup/bootc-reinstall}" info "Creating backup of current system" # Create backup directory mkdir -p "$backup_dir" # Get current deployment local current_deployment=$(ostree admin status | grep '^*' | awk '{print $2}') if [[ -z "$current_deployment" ]]; then error_exit "No current deployment found" fi # Create backup of current deployment local backup_ref="backup-$(date +%Y%m%d-%H%M%S)" if ostree commit --repo="$OSTREE_REPO" --branch="$backup_ref" --tree=ref:"$current_deployment"; then success "System backup created: $backup_ref" # Create backup metadata cat > "$backup_dir/backup-info.json" << EOF { "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "backup_ref": "$backup_ref", "original_deployment": "$current_deployment", "system_info": { "hostname": "$(hostname)", "kernel": "$(uname -r)", "architecture": "$(uname -m)", "ostree_version": "$(ostree --version | head -1)" } } EOF info "Backup metadata saved to: $backup_dir/backup-info.json" else error_exit "Failed to create system backup" fi } # Restore system from backup restore_system() { local backup_ref="$1" if [[ -z "$backup_ref" ]]; then error_exit "Backup reference required for restoration" fi info "Restoring system from backup: $backup_ref" warning "This operation will replace the current system deployment" # Confirm restoration echo -n "Are you sure you want to restore from backup? (yes/no): " read -r confirmation if [[ "$confirmation" != "yes" ]]; then info "Restoration cancelled" return 0 fi # Check if backup exists if ! ostree refs --repo="$OSTREE_REPO" | grep -q "^$backup_ref$"; then error_exit "Backup reference not found: $backup_ref" fi # Deploy backup if ostree admin deploy "$backup_ref"; then success "System restored from backup: $backup_ref" info "Reboot to activate the restored system" # Show deployment status echo -e "\n=== Restored Deployment Status ===" ostree admin status else error_exit "Failed to restore system from backup" fi } # Validate reinstallation readiness validate_reinstall() { local image_name="$1" if [[ -z "$image_name" ]]; then error_exit "Container image name required for validation" fi info "Validating reinstallation readiness for: $image_name" echo "=== Reinstallation Validation ===" # Check container image if container_lint "$image_name"; then success "✓ Container image validation passed" else error_exit "✗ Container image validation failed" fi # Check disk space local required_space=$(podman image inspect "$image_name" --format='{{.Size}}' 2>/dev/null || echo "1073741824") local available_space=$(df / | awk 'NR==2 {print $4}') local required_mb=$((required_space / 1024 / 1024)) local available_mb=$((available_space / 1024)) if [[ $available_space -gt $required_space ]]; then success "✓ Sufficient disk space: ${available_mb}MB available, ${required_mb}MB required" else error_exit "✗ Insufficient disk space: ${available_mb}MB available, ${required_mb}MB required" fi # Check OSTree repository health if ostree fsck --repo="$OSTREE_REPO" &>/dev/null; then success "✓ OSTree repository health check passed" else error_exit "✗ OSTree repository health check failed" fi # Check current deployment local current_deployment=$(ostree admin status | grep '^*' | awk '{print $2}') if [[ -n "$current_deployment" ]]; then success "✓ Current deployment found: $current_deployment" else error_exit "✗ No current deployment found" fi # Check backup directory local backup_dir="/var/backup/bootc-reinstall" if [[ -d "$backup_dir" ]]; then success "✓ Backup directory exists: $backup_dir" else info "ℹ Backup directory will be created during preparation" fi success "Reinstallation validation completed successfully" info "System is ready for reinstallation" } # Rollback reinstallation rollback_reinstall() { local backup_dir="${1:-/var/backup/bootc-reinstall}" local plan_file="$backup_dir/reinstall-plan.json" if [[ ! -f "$plan_file" ]]; then error_exit "Reinstallation plan not found: $plan_file" fi info "Rolling back reinstallation" # Get original deployment from plan local original_deployment=$(jq -r '.current_deployment' "$plan_file" 2>/dev/null) if [[ -z "$original_deployment" || "$original_deployment" == "null" ]]; then error_exit "Original deployment not found in plan" fi info "Rolling back to original deployment: $original_deployment" # Check if original deployment still exists if ! ostree refs --repo="$OSTREE_REPO" | grep -q "^$original_deployment$"; then error_exit "Original deployment not found: $original_deployment" fi # Deploy original system if ostree admin deploy "$original_deployment"; then success "Reinstallation rollback completed" info "System restored to original deployment: $original_deployment" info "Reboot to activate the original system" # Update plan status jq '.status = "rolled_back"' "$plan_file" > "$plan_file.tmp" && mv "$plan_file.tmp" "$plan_file" # Show deployment status echo -e "\n=== Rollback Deployment Status ===" ostree admin status else error_exit "Failed to rollback reinstallation" fi } # List available backups list_backups() { info "Listing available system backups" echo "=== System Backups ===" ostree refs --repo="$OSTREE_REPO" | grep "^backup-" || info "No system backups found" # Show backup details local backup_dir="/var/backup/bootc-reinstall" if [[ -f "$backup_dir/backup-info.json" ]]; then echo -e "\n=== Latest Backup Info ===" jq '.' "$backup_dir/backup-info.json" fi } # Clean old backups clean_backups() { local keep_count="${1:-5}" info "Cleaning old backups (keeping $keep_count)" # Get list of backups local backups=($(ostree refs --repo="$OSTREE_REPO" | grep "^backup-" | sort)) local total_backups=${#backups[@]} if [[ $total_backups -le $keep_count ]]; then info "No backups to clean (only $total_backups backups exist)" return 0 fi # Remove old backups local to_remove=$((total_backups - keep_count)) local removed_count=0 for ((i=0; i/dev/null; then success "✓ $service is active" systemctl status "$service" --no-pager --lines=5 else warning "✗ $service is not active" systemctl status "$service" --no-pager --lines=5 fi echo done } # Reload systemd units reload_systemd_units() { local units=("$@") if [[ ${#units[@]} -eq 0 ]]; then error_exit "No units specified for reloading" fi info "Reloading systemd units: ${units[*]}" local failed_units=() for unit in "${units[@]}"; do if systemctl reload "$unit"; then success "✓ Reloaded unit: $unit" else warning "✗ Failed to reload unit: $unit" failed_units+=("$unit") fi done if [[ ${#failed_units[@]} -gt 0 ]]; then warning "Failed to reload ${#failed_units[@]} units: ${failed_units[*]}" return 1 fi success "All units reloaded successfully" } # Mask systemd units mask_systemd_units() { local units=("$@") if [[ ${#units[@]} -eq 0 ]]; then error_exit "No units specified for masking" fi info "Masking systemd units: ${units[*]}" local failed_units=() for unit in "${units[@]}"; do if systemctl mask "$unit"; then success "✓ Masked unit: $unit" else warning "✗ Failed to mask unit: $unit" failed_units+=("$unit") fi done if [[ ${#failed_units[@]} -gt 0 ]]; then warning "Failed to mask ${#failed_units[@]} units: ${failed_units[*]}" return 1 fi success "All units masked successfully" } # Unmask systemd units unmask_systemd_units() { local units=("$@") if [[ ${#units[@]} -eq 0 ]]; then error_exit "No units specified for unmasking" fi info "Unmasking systemd units: ${units[*]}" local failed_units=() for unit in "${units[@]}"; do if systemctl unmask "$unit"; then success "✓ Unmasked unit: $unit" else warning "✗ Failed to unmask unit: $unit" failed_units+=("$unit") fi done if [[ ${#failed_units[@]} -gt 0 ]]; then warning "Failed to unmask ${#failed_units[@]} units: ${failed_units[*]}" return 1 fi success "All units unmasked successfully" } # Preset systemd units preset_systemd_units() { local units=("$@") if [[ ${#units[@]} -eq 0 ]]; then error_exit "No units specified for presetting" fi info "Presetting systemd units: ${units[*]}" local failed_units=() for unit in "${units[@]}"; do if systemctl preset "$unit"; then success "✓ Preset unit: $unit" else warning "✗ Failed to preset unit: $unit" failed_units+=("$unit") fi done if [[ ${#failed_units[@]} -gt 0 ]]; then warning "Failed to preset ${#failed_units[@]} units: ${failed_units[*]}" return 1 fi success "All units preset successfully" } # Bootc-specific systemd operations bootc_systemd_operations() { local action="$1" shift case "$action" in "setup") setup_bootc_systemd ;; "cleanup") cleanup_bootc_systemd ;; "check") check_bootc_systemd ;; *) error_exit "Unknown bootc systemd action: $action" ;; esac } # Setup bootc-specific systemd configuration setup_bootc_systemd() { info "Setting up bootc-specific systemd configuration" # Create systemd drop-in directory for bootc local drop_in_dir="/etc/systemd/system.conf.d" mkdir -p "$drop_in_dir" # Configure systemd for bootc environment cat > "$drop_in_dir/10-bootc.conf" << EOF # bootc systemd configuration [Manager] # Ensure proper handling of read-only filesystems DefaultDependencies=no # Optimize for container-based deployments DefaultTimeoutStartSec=30s DefaultTimeoutStopSec=30s # Enable proper logging for bootc LogLevel=info EOF # Reload systemd configuration if systemctl daemon-reload; then success "✓ Systemd configuration reloaded" else error_exit "Failed to reload systemd configuration" fi # Enable essential bootc services local essential_services=( "systemd-remount-fs" "systemd-sysctl" "systemd-random-seed" ) for service in "${essential_services[@]}"; do if systemctl is-enabled "$service" &>/dev/null; then info "✓ Service already enabled: $service" else if systemctl enable "$service"; then success "✓ Enabled essential service: $service" else warning "✗ Failed to enable service: $service" fi fi done success "Bootc systemd configuration setup completed" } # Cleanup bootc-specific systemd configuration cleanup_bootc_systemd() { info "Cleaning up bootc-specific systemd configuration" # Remove bootc drop-in configuration local drop_in_file="/etc/systemd/system.conf.d/10-bootc.conf" if [[ -f "$drop_in_file" ]]; then if rm "$drop_in_file"; then success "✓ Removed bootc systemd configuration" else warning "✗ Failed to remove bootc systemd configuration" fi else info "ℹ No bootc systemd configuration found" fi # Reload systemd configuration if systemctl daemon-reload; then success "✓ Systemd configuration reloaded" else warning "✗ Failed to reload systemd configuration" fi success "Bootc systemd configuration cleanup completed" } # Check bootc systemd configuration check_bootc_systemd() { info "Checking bootc systemd configuration" echo "=== Bootc Systemd Configuration ===" # Check drop-in configuration local drop_in_file="/etc/systemd/system.conf.d/10-bootc.conf" if [[ -f "$drop_in_file" ]]; then success "✓ Bootc systemd configuration exists" echo "Configuration:" cat "$drop_in_file" else info "ℹ No bootc systemd configuration found" fi # Check essential services echo -e "\n=== Essential Services Status ===" local essential_services=( "systemd-remount-fs" "systemd-sysctl" "systemd-random-seed" ) for service in "${essential_services[@]}"; do if systemctl is-enabled "$service" &>/dev/null; then success "✓ $service is enabled" else warning "✗ $service is not enabled" fi if systemctl is-active "$service" &>/dev/null; then success "✓ $service is active" else info "ℹ $service is not active" fi done # Check systemd version and features echo -e "\n=== Systemd Information ===" systemctl --version | head -1 systemctl show --property=DefaultDependencies --value } # Manage systemd targets manage_systemd_targets() { local action="$1" local target="$2" case "$action" in "set") if [[ -z "$target" ]]; then error_exit "Target name required" fi info "Setting default target: $target" if systemctl set-default "$target"; then success "Default target set to: $target" else error_exit "Failed to set default target" fi ;; "get") local current_target=$(systemctl get-default) info "Current default target: $current_target" ;; "isolate") if [[ -z "$target" ]]; then error_exit "Target name required" fi info "Isolating target: $target" if systemctl isolate "$target"; then success "Target isolated: $target" else error_exit "Failed to isolate target" fi ;; *) error_exit "Unknown target action: $action" ;; esac } # Manage systemd timers manage_systemd_timers() { local action="$1" local timer="$2" case "$action" in "list") info "Listing systemd timers" systemctl list-timers --all --no-pager ;; "enable") if [[ -z "$timer" ]]; then error_exit "Timer name required" fi info "Enabling timer: $timer" if systemctl enable "$timer"; then success "Timer enabled: $timer" else error_exit "Failed to enable timer" fi ;; "disable") if [[ -z "$timer" ]]; then error_exit "Timer name required" fi info "Disabling timer: $timer" if systemctl disable "$timer"; then success "Timer disabled: $timer" else error_exit "Failed to disable timer" fi ;; "start") if [[ -z "$timer" ]]; then error_exit "Timer name required" fi info "Starting timer: $timer" if systemctl start "$timer"; then success "Timer started: $timer" else error_exit "Failed to start timer" fi ;; "stop") if [[ -z "$timer" ]]; then error_exit "Timer name required" fi info "Stopping timer: $timer" if systemctl stop "$timer"; then success "Timer stopped: $timer" else error_exit "Failed to stop timer" fi ;; *) error_exit "Unknown timer action: $action" ;; esac } # --- END OF SCRIPTLET: 08-systemd.sh --- # ============================================================================ # User Overlay Management # ============================================================================ # bootc usroverlay equivalent # Manages transient writable overlay for /usr # Based on actual bootc usroverlay implementation usroverlay() { local action="$1" case "$action" in "start") usroverlay_start ;; "stop") usroverlay_stop ;; "status") usroverlay_status ;; *) error_exit "Unknown usroverlay action: $action" ;; esac } # Start transient overlay for /usr usroverlay_start() { info "Starting transient writable overlay for /usr" # Check if already active if detect_transient_overlay; then warning "Transient overlay is already active" usroverlay_status return 0 fi # Check if /usr is read-only if ! detect_image_based_system; then warning "/usr is already writable - no overlay needed" return 0 fi # Create overlay directories local overlay_dir="$USROVERLAY_DIR/overlay" local work_dir="$USROVERLAY_DIR/work" local upper_dir="$USROVERLAY_DIR/upper" mkdir -p "$overlay_dir" "$work_dir" "$upper_dir" # Check for processes using /usr check_usr_processes # Create overlay mount info "Creating overlayfs mount for /usr" if mount -t overlay overlay -o "lowerdir=/usr,upperdir=$upper_dir,workdir=$work_dir" "$overlay_dir"; then success "Overlay mount created successfully" # Bind mount overlay to /usr info "Binding overlay to /usr" if mount --bind "$overlay_dir" /usr; then success "Transient overlay started successfully" info "Changes to /usr will be ephemeral and lost on reboot" info "Use 'usroverlay stop' to stop the overlay" # Show overlay status usroverlay_status else error_exit "Failed to bind mount overlay to /usr" fi else error_exit "Failed to create overlay mount" fi } # Stop transient overlay usroverlay_stop() { info "Stopping transient writable overlay for /usr" # Check if overlay is active if ! detect_transient_overlay; then warning "No transient overlay is active" return 0 fi # Check for processes using /usr check_usr_processes # Check for package manager operations if check_package_manager_operations; then warning "Package manager operations detected - overlay will persist until operations complete" return 0 fi # Check for system shutdown if check_system_shutdown; then info "System shutdown detected - overlay will be automatically cleaned up" return 0 fi # Unmount /usr bind mount info "Unmounting /usr bind mount" if umount /usr; then success "Bind mount unmounted successfully" # Unmount overlay local overlay_dir="$USROVERLAY_DIR/overlay" info "Unmounting overlay" if umount "$overlay_dir"; then success "Transient overlay stopped successfully" info "All ephemeral changes to /usr have been discarded" # Clean up overlay directories rm -rf "$USROVERLAY_DIR/overlay" "$USROVERLAY_DIR/work" "$USROVERLAY_DIR/upper" info "Overlay directories cleaned up" else error_exit "Failed to unmount overlay" fi else error_exit "Failed to unmount /usr bind mount" fi } # Check overlay status usroverlay_status() { echo "=== Transient Overlay Status ===" if detect_transient_overlay; then success "✓ Transient overlay is ACTIVE" info "Changes to /usr are ephemeral and will be lost on reboot" # Show overlay details local overlay_details=$(get_overlay_details) if [[ -n "$overlay_details" ]]; then info "Overlay mount details:" echo "$overlay_details" fi # Show overlay directory usage local upper_dir="$USROVERLAY_DIR/upper" if [[ -d "$upper_dir" ]]; then local usage=$(du -sh "$upper_dir" 2>/dev/null | cut -f1 || echo "unknown") info "Overlay usage: $usage" fi # Check for package manager operations if check_package_manager_operations; then warning "⚠️ Package manager operations detected - overlay will persist" fi # Check for system shutdown if check_system_shutdown; then warning "⚠️ System shutdown detected - overlay will be cleaned up" fi else info "ℹ No transient overlay is active" # Check if /usr is read-only if detect_image_based_system; then info "ℹ /usr is read-only (image-based system)" info "Use 'usroverlay start' to create a transient overlay" else info "ℹ /usr is writable (traditional system)" fi fi echo "" } # --- END OF SCRIPTLET: 09-usroverlay.sh --- # ============================================================================ # Kernel Arguments Management # ============================================================================ # Kernel arguments management functions # Kernel arguments management with TOML configuration and deployment integration # Enhanced with DKMS and NVIDIA support # DKMS and NVIDIA kernel argument management manage_dkms_kargs() { local action="$1" shift case "$action" in "add-dkms") add_dkms_kernel_args "$@" ;; "add-nvidia") add_nvidia_kernel_args "$@" ;; "remove-dkms") remove_dkms_kernel_args "$@" ;; "remove-nvidia") remove_nvidia_kernel_args "$@" ;; "list-dkms") list_dkms_kernel_args "$@" ;; "list-nvidia") list_nvidia_kernel_args "$@" ;; "configure-nvidia-prime") configure_nvidia_prime_kargs "$@" ;; *) error_exit "Unknown DKMS/NVIDIA kargs action: $action" ;; esac } # Add DKMS-related kernel arguments add_dkms_kernel_args() { local module_name="$1" local kernel_version="${2:-$(uname -r)}" if [[ -z "$module_name" ]]; then error_exit "DKMS module name required" fi info "Adding DKMS kernel arguments for module: $module_name" # Common DKMS kernel arguments local dkms_args=( "module_blacklist=nouveau" # Blacklist open-source NVIDIA driver "nvidia-drm.modeset=1" # Enable NVIDIA DRM modesetting "nvidia-drm.fbdev=1" # Enable NVIDIA framebuffer ) # Add module-specific arguments case "$module_name" in "nvidia"|"nvidia-driver"*) dkms_args+=( "nvidia.NVreg_UsePageAttributeTable=1" "nvidia.NVreg_EnablePCIeGen3=1" "nvidia.NVreg_InitializeSystemMemoryAllocations=1" ) ;; "virtualbox"|"virtualbox-dkms") dkms_args+=( "vboxdrv.force_async_tsc=1" "vboxdrv.timer_method=1" ) ;; "vmware"|"open-vm-tools-dkms") dkms_args+=( "vmw_vmci.max_pages=512" "vmw_vmci.max_queues=64" ) ;; *) info "No specific kernel arguments for module: $module_name" ;; esac # Add all DKMS arguments for arg in "${dkms_args[@]}"; do add_kernel_arg "$arg" done success "DKMS kernel arguments added for module: $module_name" info "Arguments will be applied on next deployment" } # Add NVIDIA-specific kernel arguments add_nvidia_kernel_args() { local driver_version="${1:-auto}" info "Adding NVIDIA kernel arguments for driver version: $driver_version" # NVIDIA-specific kernel arguments local nvidia_args=( "module_blacklist=nouveau" # Blacklist open-source NVIDIA driver "nvidia-drm.modeset=1" # Enable NVIDIA DRM modesetting "nvidia-drm.fbdev=1" # Enable NVIDIA framebuffer "nvidia.NVreg_UsePageAttributeTable=1" "nvidia.NVreg_EnablePCIeGen3=1" "nvidia.NVreg_InitializeSystemMemoryAllocations=1" "nvidia.NVreg_PreserveVideoMemoryAllocations=1" "nvidia.NVreg_EnableMSI=1" "nvidia.NVreg_EnablePCIeGen3=1" "nvidia.NVreg_UsePageAttributeTable=1" ) # Add gaming-specific arguments local gaming_args=( "nvidia.NVreg_RegistryDwords=OverrideMaxPerf=0x1" "nvidia.NVreg_RegistryDwords=PowerMizerEnable=0x1" "nvidia.NVreg_RegistryDwords=PowerMizerLevel=0x1" "nvidia.NVreg_RegistryDwords=PowerMizerDefault=0x1" ) # Add all NVIDIA arguments for arg in "${nvidia_args[@]}" "${gaming_args[@]}"; do add_kernel_arg "$arg" done success "NVIDIA kernel arguments added for driver version: $driver_version" info "Arguments will be applied on next deployment" } # Remove DKMS-related kernel arguments remove_dkms_kernel_args() { local module_name="$1" if [[ -z "$module_name" ]]; then error_exit "DKMS module name required" fi info "Removing DKMS kernel arguments for module: $module_name" # Common DKMS kernel arguments to remove local dkms_args=( "module_blacklist=nouveau" "nvidia-drm.modeset=1" "nvidia-drm.fbdev=1" ) # Add module-specific arguments to remove case "$module_name" in "nvidia"|"nvidia-driver"*) dkms_args+=( "nvidia.NVreg_UsePageAttributeTable=1" "nvidia.NVreg_EnablePCIeGen3=1" "nvidia.NVreg_InitializeSystemMemoryAllocations=1" ) ;; "virtualbox"|"virtualbox-dkms") dkms_args+=( "vboxdrv.force_async_tsc=1" "vboxdrv.timer_method=1" ) ;; "vmware"|"open-vm-tools-dkms") dkms_args+=( "vmw_vmci.max_pages=512" "vmw_vmci.max_queues=64" ) ;; esac # Remove all DKMS arguments for arg in "${dkms_args[@]}"; do remove_kernel_arg "$arg" done success "DKMS kernel arguments removed for module: $module_name" } # Remove NVIDIA-specific kernel arguments remove_nvidia_kernel_args() { info "Removing NVIDIA kernel arguments" # NVIDIA-specific kernel arguments to remove local nvidia_args=( "module_blacklist=nouveau" "nvidia-drm.modeset=1" "nvidia-drm.fbdev=1" "nvidia.NVreg_UsePageAttributeTable=1" "nvidia.NVreg_EnablePCIeGen3=1" "nvidia.NVreg_InitializeSystemMemoryAllocations=1" "nvidia.NVreg_PreserveVideoMemoryAllocations=1" "nvidia.NVreg_EnableMSI=1" "nvidia.NVreg_EnablePCIeGen3=1" "nvidia.NVreg_UsePageAttributeTable=1" "nvidia.NVreg_RegistryDwords=OverrideMaxPerf=0x1" "nvidia.NVreg_RegistryDwords=PowerMizerEnable=0x1" "nvidia.NVreg_RegistryDwords=PowerMizerLevel=0x1" "nvidia.NVreg_RegistryDwords=PowerMizerDefault=0x1" ) # Remove all NVIDIA arguments for arg in "${nvidia_args[@]}"; do remove_kernel_arg "$arg" done success "NVIDIA kernel arguments removed" } # List DKMS-related kernel arguments list_dkms_kernel_args() { info "Listing DKMS kernel arguments" echo "=== DKMS Kernel Arguments ===" # Check for DKMS-related arguments in current kernel local current_args=$(cat /proc/cmdline 2>/dev/null || echo "") if [[ -n "$current_args" ]]; then echo "Current DKMS arguments:" echo "$current_args" | tr ' ' '\n' | grep -E "(module_blacklist|nvidia|vboxdrv|vmw_vmci)" | sort || echo " None" fi # Check for DKMS-related arguments in pending local pending_file="$KARGS_DIR/pending.toml" if [[ -f "$pending_file" ]]; then echo -e "\nPending DKMS arguments:" if command -v toml2json &>/dev/null; then toml2json "$pending_file" | jq -r '.kargs[]?' 2>/dev/null | grep -E "(module_blacklist|nvidia|vboxdrv|vmw_vmci)" | sort || echo " None" else grep -E "(module_blacklist|nvidia|vboxdrv|vmw_vmci)" "$pending_file" | sed 's/^[[:space:]]*"\([^"]*\)"[[:space:]]*$/\1/' || echo " None" fi fi } # List NVIDIA-specific kernel arguments list_nvidia_kernel_args() { info "Listing NVIDIA kernel arguments" echo "=== NVIDIA Kernel Arguments ===" # Check for NVIDIA-related arguments in current kernel local current_args=$(cat /proc/cmdline 2>/dev/null || echo "") if [[ -n "$current_args" ]]; then echo "Current NVIDIA arguments:" echo "$current_args" | tr ' ' '\n' | grep -E "(module_blacklist=nouveau|nvidia)" | sort || echo " None" fi # Check for NVIDIA-related arguments in pending local pending_file="$KARGS_DIR/pending.toml" if [[ -f "$pending_file" ]]; then echo -e "\nPending NVIDIA arguments:" if command -v toml2json &>/dev/null; then toml2json "$pending_file" | jq -r '.kargs[]?' 2>/dev/null | grep -E "(module_blacklist=nouveau|nvidia)" | sort || echo " None" else grep -E "(module_blacklist=nouveau|nvidia)" "$pending_file" | sed 's/^[[:space:]]*"\([^"]*\)"[[:space:]]*$/\1/' || echo " None" fi fi } # Configure NVIDIA Prime kernel arguments configure_nvidia_prime_kargs() { local gpu_mode="${1:-auto}" info "Configuring NVIDIA Prime kernel arguments for mode: $gpu_mode" # Remove existing NVIDIA Prime arguments remove_kernel_arg "nvidia-drm.modeset=1" remove_kernel_arg "nvidia-drm.fbdev=1" case "$gpu_mode" in "nvidia"|"performance") # Performance mode - use NVIDIA GPU add_kernel_arg "nvidia-drm.modeset=1" add_kernel_arg "nvidia-drm.fbdev=1" add_kernel_arg "module_blacklist=nouveau" success "NVIDIA Prime configured for performance mode" ;; "integrated"|"intel"|"power-save") # Power save mode - use integrated GPU remove_kernel_arg "module_blacklist=nouveau" success "NVIDIA Prime configured for power save mode" ;; "auto"|"dynamic") # Auto mode - let system decide add_kernel_arg "nvidia-drm.modeset=1" success "NVIDIA Prime configured for auto mode" ;; *) error_exit "Invalid GPU mode: $gpu_mode (use: nvidia, integrated, auto)" ;; esac info "NVIDIA Prime kernel arguments configured for mode: $gpu_mode" info "Arguments will be applied on next deployment" } # Kernel arguments operations manage_kernel_args() { local action="$1" shift case "$action" in "list") list_kernel_args "$@" ;; "add") add_kernel_arg "$@" ;; "remove") remove_kernel_arg "$@" ;; "clear") clear_kernel_args "$@" ;; "show") show_kernel_args "$@" ;; "apply") apply_kernel_args "$@" ;; "reset") reset_kernel_args "$@" ;; *) error_exit "Unknown kargs action: $action" ;; esac } # List current kernel arguments list_kernel_args() { local format="${1:-human}" case "$format" in "human") list_kernel_args_human ;; "json") list_kernel_args_json ;; "toml") list_kernel_args_toml ;; *) error_exit "Unknown format: $format" ;; esac } # List kernel arguments in human-readable format list_kernel_args_human() { info "Listing kernel arguments" echo "=== Current Kernel Arguments ===" local current_args=$(cat /proc/cmdline 2>/dev/null || echo "") if [[ -n "$current_args" ]]; then echo "$current_args" | tr ' ' '\n' | sort else info "No kernel arguments found" fi echo -e "\n=== Pending Kernel Arguments ===" local pending_file="$KARGS_DIR/pending.toml" if [[ -f "$pending_file" ]]; then if command -v yq &>/dev/null; then yq -r '.kargs[]?' "$pending_file" 2>/dev/null || info "No pending kernel arguments" elif command -v toml2json &>/dev/null; then toml2json "$pending_file" | jq -r '.kargs[]?' 2>/dev/null || info "No pending kernel arguments" else grep -E '^[[:space:]]*"[^"]*"[[:space:]]*$' "$pending_file" | sed 's/^[[:space:]]*"\([^"]*\)"[[:space:]]*$/\1/' || info "No pending kernel arguments" fi else info "No pending kernel arguments" fi } # List kernel arguments in JSON format list_kernel_args_json() { local current_args=$(cat /proc/cmdline 2>/dev/null || echo "") local pending_file="$KARGS_DIR/pending.toml" # Build JSON structure local json_output="{" json_output+="\"current\":[" if [[ -n "$current_args" ]]; then local first=true while IFS= read -r arg; do if [[ -n "$arg" ]]; then if [[ "$first" == "true" ]]; then first=false else json_output+="," fi json_output+="\"$arg\"" fi done <<< "$(echo "$current_args" | tr ' ' '\n')" fi json_output+="],\"pending\":[" if [[ -f "$pending_file" ]]; then if command -v toml2json &>/dev/null; then local pending_args=$(toml2json "$pending_file" | jq -r '.kargs[]?' 2>/dev/null) if [[ -n "$pending_args" ]]; then local first=true while IFS= read -r arg; do if [[ -n "$arg" ]]; then if [[ "$first" == "true" ]]; then first=false else json_output+="," fi json_output+="\"$arg\"" fi done <<< "$pending_args" fi fi fi json_output+="]}" echo "$json_output" } # List kernel arguments in TOML format list_kernel_args_toml() { local current_args=$(cat /proc/cmdline 2>/dev/null || echo "") local pending_file="$KARGS_DIR/pending.toml" echo "# Current kernel arguments" echo "current = [" if [[ -n "$current_args" ]]; then while IFS= read -r arg; do if [[ -n "$arg" ]]; then echo " \"$arg\"," fi done <<< "$(echo "$current_args" | tr ' ' '\n')" fi echo "]" echo -e "\n# Pending kernel arguments" if [[ -f "$pending_file" ]]; then cat "$pending_file" else echo "pending = []" fi } # Add kernel argument add_kernel_arg() { local arg="$1" if [[ -z "$arg" ]]; then error_exit "Kernel argument required" fi info "Adding kernel argument: $arg" # Create kargs directory if it doesn't exist mkdir -p "$KARGS_DIR" # Check if argument already exists local pending_file="$KARGS_DIR/pending.toml" if [[ -f "$pending_file" ]]; then if grep -q "\"$arg\"" "$pending_file" 2>/dev/null; then warning "Kernel argument already exists: $arg" return 0 fi fi # Add argument to pending file if [[ ! -f "$pending_file" ]]; then cat > "$pending_file" << EOF # Pending kernel arguments # These arguments will be applied on next deployment kargs = [ EOF else # Remove closing bracket and add comma sed -i '$ s/]$/,\n/' "$pending_file" fi # Add new argument echo " \"$arg\"," >> "$pending_file" echo "]" >> "$pending_file" success "Kernel argument added: $arg" info "Argument will be applied on next deployment" } # Remove kernel argument remove_kernel_arg() { local arg="$1" if [[ -z "$arg" ]]; then error_exit "Kernel argument required" fi info "Removing kernel argument: $arg" local pending_file="$KARGS_DIR/pending.toml" if [[ ! -f "$pending_file" ]]; then warning "No pending kernel arguments found" return 0 fi # Remove argument from pending file if grep -q "\"$arg\"" "$pending_file" 2>/dev/null; then # Create temporary file without the argument local temp_file=$(mktemp) local in_kargs=false local removed=false while IFS= read -r line; do if [[ "$line" =~ ^kargs[[:space:]]*=[[:space:]]*\[ ]]; then in_kargs=true echo "$line" >> "$temp_file" elif [[ "$in_kargs" == "true" && "$line" =~ ^[[:space:]]*\"$arg\"[[:space:]]*,?$ ]]; then removed=true # Skip this line (remove the argument) continue elif [[ "$in_kargs" == "true" && "$line" =~ ^[[:space:]]*\] ]]; then in_kargs=false echo "$line" >> "$temp_file" else echo "$line" >> "$temp_file" fi done < "$pending_file" if [[ "$removed" == "true" ]]; then mv "$temp_file" "$pending_file" success "Kernel argument removed: $arg" else rm "$temp_file" warning "Kernel argument not found in pending list: $arg" fi else warning "Kernel argument not found in pending list: $arg" fi } # Clear all pending kernel arguments clear_kernel_args() { info "Clearing all pending kernel arguments" local pending_file="$KARGS_DIR/pending.toml" if [[ -f "$pending_file" ]]; then if rm "$pending_file"; then success "All pending kernel arguments cleared" else error_exit "Failed to clear pending kernel arguments" fi else info "No pending kernel arguments to clear" fi } # Show kernel arguments in detail show_kernel_args() { local format="${1:-human}" info "Showing kernel arguments in $format format" case "$format" in "human") show_kernel_args_human ;; "json") show_kernel_args_json ;; "toml") show_kernel_args_toml ;; *) error_exit "Unknown format: $format" ;; esac } # Show kernel arguments in human-readable format show_kernel_args_human() { echo "=== Kernel Arguments Details ===" # Current kernel arguments echo "Current Kernel Arguments:" local current_args=$(cat /proc/cmdline 2>/dev/null || echo "") if [[ -n "$current_args" ]]; then echo " $current_args" else echo " None" fi # Pending kernel arguments echo -e "\nPending Kernel Arguments:" local pending_file="$KARGS_DIR/pending.toml" if [[ -f "$pending_file" ]]; then if command -v toml2json &>/dev/null; then local pending_args=$(toml2json "$pending_file" | jq -r '.kargs[]?' 2>/dev/null) if [[ -n "$pending_args" ]]; then while IFS= read -r arg; do if [[ -n "$arg" ]]; then echo " $arg" fi done <<< "$pending_args" else echo " None" fi else echo " (Install toml2json for detailed view)" cat "$pending_file" fi else echo " None" fi # Kernel information echo -e "\nKernel Information:" echo " Version: $(uname -r)" echo " Architecture: $(uname -m)" echo " Boot time: $(uptime -s)" } # Show kernel arguments in JSON format show_kernel_args_json() { local current_args=$(cat /proc/cmdline 2>/dev/null || echo "") local pending_file="$KARGS_DIR/pending.toml" # Build detailed JSON structure local json_output="{" json_output+="\"current\":\"$current_args\"," json_output+="\"pending\":[" if [[ -f "$pending_file" ]]; then if command -v toml2json &>/dev/null; then local pending_args=$(toml2json "$pending_file" | jq -r '.kargs[]?' 2>/dev/null) if [[ -n "$pending_args" ]]; then local first=true while IFS= read -r arg; do if [[ -n "$arg" ]]; then if [[ "$first" == "true" ]]; then first=false else json_output+="," fi json_output+="\"$arg\"" fi done <<< "$pending_args" fi fi fi json_output+="]," json_output+="\"kernel_info\":{" json_output+="\"version\":\"$(uname -r)\"," json_output+="\"architecture\":\"$(uname -m)\"," json_output+="\"boot_time\":\"$(uptime -s)\"" json_output+="}" json_output+="}" echo "$json_output" } # Show kernel arguments in TOML format show_kernel_args_toml() { local current_args=$(cat /proc/cmdline 2>/dev/null || echo "") local pending_file="$KARGS_DIR/pending.toml" echo "# Kernel arguments configuration" echo "current = \"$current_args\"" echo "" if [[ -f "$pending_file" ]]; then cat "$pending_file" else echo "# No pending kernel arguments" echo "pending = []" fi echo "" echo "# Kernel information" echo "[kernel_info]" echo "version = \"$(uname -r)\"" echo "architecture = \"$(uname -m)\"" echo "boot_time = \"$(uptime -s)\"" } # Apply kernel arguments immediately (for testing) apply_kernel_args() { info "Applying kernel arguments immediately (for testing)" warning "This will modify the current boot configuration" # Confirm application echo -n "Are you sure you want to apply kernel arguments now? (yes/no): " read -r confirmation if [[ "$confirmation" != "yes" ]]; then info "Kernel arguments application cancelled" return 0 fi local pending_file="$KARGS_DIR/pending.toml" if [[ ! -f "$pending_file" ]]; then info "No pending kernel arguments to apply" return 0 fi # Extract pending arguments local pending_args="" if command -v toml2json &>/dev/null; then pending_args=$(toml2json "$pending_file" | jq -r '.kargs[]?' 2>/dev/null | tr '\n' ' ') fi if [[ -n "$pending_args" ]]; then # Apply arguments using grubby (if available) if command -v grubby &>/dev/null; then for arg in $pending_args; do info "Applying kernel argument: $arg" if grubby --args="$arg" --update-kernel=ALL; then success "✓ Applied: $arg" else warning "✗ Failed to apply: $arg" fi done success "Kernel arguments applied successfully" info "Reboot to activate the new kernel arguments" else error_exit "grubby not available - cannot apply kernel arguments" fi else info "No pending kernel arguments to apply" fi } # Reset kernel arguments to defaults reset_kernel_args() { info "Resetting kernel arguments to defaults" warning "This will remove all custom kernel arguments" # Confirm reset echo -n "Are you sure you want to reset kernel arguments? (yes/no): " read -r confirmation if [[ "$confirmation" != "yes" ]]; then info "Kernel arguments reset cancelled" return 0 fi # Clear pending arguments clear_kernel_args # Reset current kernel arguments (if grubby available) if command -v grubby &>/dev/null; then info "Resetting current kernel arguments" if grubby --remove-args="$(cat /proc/cmdline)" --update-kernel=ALL; then success "Current kernel arguments reset" info "Reboot to activate default kernel arguments" else warning "Failed to reset current kernel arguments" fi else info "grubby not available - only cleared pending arguments" fi success "Kernel arguments reset completed" } # --- END OF SCRIPTLET: 10-kargs.sh --- # ============================================================================ # Secrets and Authentication Management # ============================================================================ # Secrets and authentication management functions # Secure secrets management with registry authentication and credential sync # Enhanced with NVIDIA Prime configuration # NVIDIA Prime configuration management manage_nvidia_prime() { local action="$1" shift case "$action" in "setup") setup_nvidia_prime "$@" ;; "configure") configure_nvidia_prime "$@" ;; "switch") switch_nvidia_prime "$@" ;; "status") check_nvidia_prime_status "$@" ;; "reset") reset_nvidia_prime "$@" ;; *) error_exit "Unknown NVIDIA Prime action: $action" ;; esac } # Setup NVIDIA Prime configuration setup_nvidia_prime() { local gpu_mode="${1:-auto}" info "Setting up NVIDIA Prime configuration for mode: $gpu_mode" # Check if NVIDIA drivers are installed if ! command -v nvidia-smi &>/dev/null; then error_exit "NVIDIA drivers not installed. Install NVIDIA drivers first." fi # Create NVIDIA Prime configuration directory local prime_dir="/etc/prime" mkdir -p "$prime_dir" # Create display configuration local display_config="$prime_dir/display" case "$gpu_mode" in "nvidia"|"performance") cat > "$display_config" << EOF # NVIDIA Prime configuration - Performance mode # Use NVIDIA GPU for all applications nvidia EOF ;; "integrated"|"intel"|"power-save") cat > "$display_config" << EOF # NVIDIA Prime configuration - Power save mode # Use integrated GPU for all applications intel EOF ;; "auto"|"dynamic") cat > "$display_config" << EOF # NVIDIA Prime configuration - Auto mode # Let system decide which GPU to use auto EOF ;; *) error_exit "Invalid GPU mode: $gpu_mode (use: nvidia, integrated, auto)" ;; esac # Set proper permissions chmod 644 "$display_config" chown root:root "$display_config" # Create NVIDIA Prime configuration file local prime_config="$prime_dir/prime.conf" cat > "$prime_config" << EOF # NVIDIA Prime configuration # This file controls NVIDIA Prime behavior # GPU switching mode MODE=$gpu_mode # NVIDIA GPU PCI ID (auto-detected) NVIDIA_GPU_ID=$(lspci | grep -i nvidia | head -1 | cut -d' ' -f1 || echo "auto") # Integrated GPU PCI ID (auto-detected) INTEL_GPU_ID=$(lspci | grep -i intel | grep -i vga | head -1 | cut -d' ' -f1 || echo "auto") # Power management POWER_MANAGEMENT=auto # Application profiles APPLICATION_PROFILES=enabled # Logging LOG_LEVEL=info EOF # Set proper permissions chmod 644 "$prime_config" chown root:root "$prime_config" # Install NVIDIA Prime utilities if not present if ! command -v prime-select &>/dev/null; then info "Installing NVIDIA Prime utilities" if command -v apt-get &>/dev/null; then apt-get update && apt-get install -y nvidia-prime-applet elif command -v dnf &>/dev/null; then dnf install -y nvidia-prime elif command -v pacman &>/dev/null; then pacman -S nvidia-prime else warning "Package manager not found - please install nvidia-prime manually" fi fi # Apply the configuration switch_nvidia_prime "$gpu_mode" success "NVIDIA Prime configuration setup complete for mode: $gpu_mode" info "Configuration files: $display_config, $prime_config" info "Use 'switch' action to change GPU mode" } # Configure NVIDIA Prime settings configure_nvidia_prime() { local setting="$1" local value="$2" if [[ -z "$setting" || -z "$value" ]]; then error_exit "Setting and value required for configure" fi info "Configuring NVIDIA Prime setting: $setting = $value" local prime_config="/etc/prime/prime.conf" if [[ ! -f "$prime_config" ]]; then error_exit "NVIDIA Prime not configured. Run setup first." fi # Update configuration file if grep -q "^$setting=" "$prime_config"; then # Update existing setting sed -i "s/^$setting=.*/$setting=$value/" "$prime_config" else # Add new setting echo "$setting=$value" >> "$prime_config" fi success "NVIDIA Prime setting configured: $setting = $value" } # Switch NVIDIA Prime GPU mode switch_nvidia_prime() { local gpu_mode="${1:-auto}" info "Switching NVIDIA Prime to mode: $gpu_mode" # Check if prime-select is available if ! command -v prime-select &>/dev/null; then error_exit "prime-select not available. Install nvidia-prime package." fi case "$gpu_mode" in "nvidia"|"performance") if prime-select nvidia; then success "Switched to NVIDIA GPU (performance mode)" else error_exit "Failed to switch to NVIDIA GPU" fi ;; "integrated"|"intel"|"power-save") if prime-select intel; then success "Switched to integrated GPU (power save mode)" else error_exit "Failed to switch to integrated GPU" fi ;; "auto"|"dynamic") if prime-select on-demand; then success "Switched to auto mode (dynamic GPU switching)" else error_exit "Failed to switch to auto mode" fi ;; *) error_exit "Invalid GPU mode: $gpu_mode (use: nvidia, integrated, auto)" ;; esac # Update display configuration local display_config="/etc/prime/display" if [[ -f "$display_config" ]]; then case "$gpu_mode" in "nvidia"|"performance") echo "nvidia" > "$display_config" ;; "integrated"|"intel"|"power-save") echo "intel" > "$display_config" ;; "auto"|"dynamic") echo "auto" > "$display_config" ;; esac fi info "GPU mode switched to: $gpu_mode" info "Logout and login again for changes to take effect" } # Check NVIDIA Prime status check_nvidia_prime_status() { info "Checking NVIDIA Prime status" echo "=== NVIDIA Prime Status ===" # Check if NVIDIA drivers are installed if command -v nvidia-smi &>/dev/null; then success "✓ NVIDIA drivers installed" echo "Driver version: $(nvidia-smi --query-gpu=driver_version --format=csv,noheader,nounits | head -1)" else info "ℹ NVIDIA drivers not installed" fi # Check if prime-select is available if command -v prime-select &>/dev/null; then success "✓ NVIDIA Prime utilities available" echo "Current GPU mode: $(prime-select query)" else info "ℹ NVIDIA Prime utilities not available" fi # Check display configuration local display_config="/etc/prime/display" if [[ -f "$display_config" ]]; then success "✓ Display configuration exists" echo "Display mode: $(cat "$display_config")" else info "ℹ No display configuration found" fi # Check prime configuration local prime_config="/etc/prime/prime.conf" if [[ -f "$prime_config" ]]; then success "✓ Prime configuration exists" echo "Configuration:" cat "$prime_config" | grep -v "^#" | grep -v "^$" | sed 's/^/ /' else info "ℹ No prime configuration found" fi # Check GPU information echo -e "\n=== GPU Information ===" if command -v lspci &>/dev/null; then echo "NVIDIA GPUs:" lspci | grep -i nvidia | sed 's/^/ /' || echo " None found" echo -e "\nIntegrated GPUs:" lspci | grep -i intel | grep -i vga | sed 's/^/ /' || echo " None found" fi # Check loaded modules echo -e "\n=== Loaded GPU Modules ===" if lsmod | grep -q nvidia; then success "✓ NVIDIA modules loaded" lsmod | grep nvidia | sed 's/^/ /' else info "ℹ NVIDIA modules not loaded" fi if lsmod | grep -q i915; then success "✓ Intel modules loaded" lsmod | grep i915 | sed 's/^/ /' else info "ℹ Intel modules not loaded" fi } # Reset NVIDIA Prime configuration reset_nvidia_prime() { info "Resetting NVIDIA Prime configuration" # Remove configuration files local prime_dir="/etc/prime" if [[ -d "$prime_dir" ]]; then if rm -rf "$prime_dir"; then success "✓ Removed NVIDIA Prime configuration directory" else warning "✗ Failed to remove configuration directory" fi fi # Reset to integrated GPU if possible if command -v prime-select &>/dev/null; then if prime-select intel; then success "✓ Reset to integrated GPU" else warning "✗ Failed to reset to integrated GPU" fi fi success "NVIDIA Prime configuration reset complete" info "Run setup to reconfigure NVIDIA Prime" } # Secrets operations manage_secrets() { local action="$1" shift case "$action" in "setup") setup_registry_auth "$@" ;; "sync") sync_credentials "$@" ;; "status") check_auth_status "$@" ;; "list") list_registries "$@" ;; "remove") remove_registry_auth "$@" ;; "export") export_credentials "$@" ;; "import") import_credentials "$@" ;; *) error_exit "Unknown secrets action: $action" ;; esac } # Setup registry authentication setup_registry_auth() { local registry="$1" local username="$2" local password="$3" if [[ -z "$registry" || -z "$username" ]]; then error_exit "Registry and username required" fi info "Setting up authentication for registry: $registry" # Get password interactively if not provided if [[ -z "$password" ]]; then echo -n "Enter password for $username@$registry: " read -rs password echo if [[ -z "$password" ]]; then error_exit "Password cannot be empty" fi else warning "Password provided as argument - this may be logged in shell history" warning "Consider using interactive mode for better security" fi # Create auth directory local auth_dir="/etc/ostree" mkdir -p "$auth_dir" # Create or update auth.json local auth_file="$auth_dir/auth.json" local temp_auth=$(mktemp) # Read existing auth.json if it exists if [[ -f "$auth_file" ]]; then cp "$auth_file" "$temp_auth" else echo '{"registries": {}}' > "$temp_auth" fi # Add or update registry authentication if command -v jq &>/dev/null; then # Use jq to safely update JSON jq --arg reg "$registry" \ --arg user "$username" \ --arg pass "$password" \ '.registries[$reg] = {"username": $user, "password": $pass}' \ "$temp_auth" > "$auth_file" if [[ $? -eq 0 ]]; then success "✓ Authentication configured for $registry" else error_exit "Failed to update authentication file" fi else # Fallback to manual JSON manipulation (basic) warning "jq not available - using basic JSON manipulation" # This is a simplified approach - in production, jq should be used local auth_json=$(cat "$temp_auth" 2>/dev/null || echo '{"registries": {}}') # Simple replacement (not recommended for production) echo "$auth_json" | sed "s/\"registries\": {/\"registries\": {\"$registry\": {\"username\": \"$username\", \"password\": \"$password\"},/" > "$auth_file" success "✓ Authentication configured for $registry (basic mode)" fi # Set proper permissions chmod 600 "$auth_file" chown root:root "$auth_file" # Clean up rm -f "$temp_auth" info "Authentication file: $auth_file" info "Use 'sync' action to synchronize with podman credentials" } # Synchronize credentials with podman sync_credentials() { info "Synchronizing credentials with podman" local auth_file="/etc/ostree/auth.json" if [[ ! -f "$auth_file" ]]; then info "No authentication file found - nothing to sync" return 0 fi # Check if podman is available if ! command -v podman &>/dev/null; then error_exit "podman not available" fi # Extract registries from auth.json if command -v jq &>/dev/null; then local registries=$(jq -r '.registries | keys[]' "$auth_file" 2>/dev/null) if [[ -n "$registries" ]]; then local sync_count=0 while IFS= read -r registry; do if [[ -n "$registry" ]]; then local username=$(jq -r ".registries[\"$registry\"].username" "$auth_file" 2>/dev/null) local password=$(jq -r ".registries[\"$registry\"].password" "$auth_file" 2>/dev/null) if [[ -n "$username" && -n "$password" && "$username" != "null" && "$password" != "null" ]]; then info "Syncing credentials for $registry" if echo "$password" | podman login "$registry" --username "$username" --password-stdin; then success "✓ Synced credentials for $registry" ((sync_count++)) else warning "✗ Failed to sync credentials for $registry" fi fi fi done <<< "$registries" success "Synchronized credentials for $sync_count registries" else info "No registries found in authentication file" fi else warning "jq not available - cannot parse authentication file" info "Please install jq for credential synchronization" fi } # Check authentication status check_auth_status() { info "Checking authentication status" echo "=== Registry Authentication Status ===" # Check OSTree authentication local auth_file="/etc/ostree/auth.json" if [[ -f "$auth_file" ]]; then success "✓ OSTree authentication file exists" if command -v jq &>/dev/null; then local registry_count=$(jq '.registries | length' "$auth_file" 2>/dev/null || echo "0") info "Configured registries: $registry_count" # List configured registries local registries=$(jq -r '.registries | keys[]' "$auth_file" 2>/dev/null) if [[ -n "$registries" ]]; then echo "Configured registries:" while IFS= read -r registry; do if [[ -n "$registry" ]]; then local username=$(jq -r ".registries[\"$registry\"].username" "$auth_file" 2>/dev/null) echo " - $registry (user: $username)" fi done <<< "$registries" fi else info "ℹ Install jq for detailed registry information" fi else info "ℹ No OSTree authentication file found" fi # Check podman credentials echo -e "\n=== Podman Credentials ===" if command -v podman &>/dev/null; then local podman_auth_dir="$HOME/.config/containers" if [[ -f "$podman_auth_dir/auth.json" ]]; then success "✓ Podman authentication file exists" local podman_registries=$(jq -r '.auths | keys[]' "$podman_auth_dir/auth.json" 2>/dev/null | wc -l) info "Podman registries: $podman_registries" else info "ℹ No podman authentication file found" fi else warning "✗ podman not available" fi # Check system-wide credentials echo -e "\n=== System Credentials ===" local system_auth_files=( "/etc/containers/auth.json" "/run/containers/auth.json" "/var/lib/containers/auth.json" ) local found_system_auth=false for auth_file in "${system_auth_files[@]}"; do if [[ -f "$auth_file" ]]; then success "✓ System authentication file: $auth_file" found_system_auth=true fi done if [[ "$found_system_auth" == "false" ]]; then info "ℹ No system-wide authentication files found" fi } # List configured registries list_registries() { local format="${1:-human}" case "$format" in "human") list_registries_human ;; "json") list_registries_json ;; *) error_exit "Unknown format: $format" ;; esac } # List registries in human-readable format list_registries_human() { info "Listing configured registries" local auth_file="/etc/ostree/auth.json" if [[ ! -f "$auth_file" ]]; then info "No authentication file found" return 0 fi if command -v jq &>/dev/null; then local registries=$(jq -r '.registries | keys[]' "$auth_file" 2>/dev/null) if [[ -n "$registries" ]]; then echo "=== Configured Registries ===" while IFS= read -r registry; do if [[ -n "$registry" ]]; then local username=$(jq -r ".registries[\"$registry\"].username" "$auth_file" 2>/dev/null) echo "Registry: $registry" echo " Username: $username" echo " Status: Configured" echo fi done <<< "$registries" else info "No registries configured" fi else warning "jq not available - cannot parse authentication file" fi } # List registries in JSON format list_registries_json() { local auth_file="/etc/ostree/auth.json" if [[ -f "$auth_file" ]]; then if command -v jq &>/dev/null; then jq '.registries | to_entries | map({registry: .key, username: .value.username, configured: true})' "$auth_file" else echo '{"error": "jq not available"}' fi else echo '{"registries": []}' fi } # Remove registry authentication remove_registry_auth() { local registry="$1" if [[ -z "$registry" ]]; then error_exit "Registry name required" fi info "Removing authentication for registry: $registry" local auth_file="/etc/ostree/auth.json" if [[ ! -f "$auth_file" ]]; then warning "No authentication file found" return 0 fi if command -v jq &>/dev/null; then # Check if registry exists if jq -e ".registries[\"$registry\"]" "$auth_file" &>/dev/null; then # Remove registry from auth.json local temp_auth=$(mktemp) jq --arg reg "$registry" 'del(.registries[$reg])' "$auth_file" > "$temp_auth" if [[ $? -eq 0 ]]; then mv "$temp_auth" "$auth_file" success "✓ Removed authentication for $registry" # Also remove from podman if available if command -v podman &>/dev/null; then info "Removing from podman credentials" podman logout "$registry" &>/dev/null || true fi else error_exit "Failed to remove registry authentication" fi else warning "Registry not found: $registry" fi else error_exit "jq not available - cannot remove registry authentication" fi } # Export credentials (for backup/migration) export_credentials() { local output_file="$1" if [[ -z "$output_file" ]]; then output_file="auth-backup-$(date +%Y%m%d-%H%M%S).json" fi info "Exporting credentials to: $output_file" local auth_file="/etc/ostree/auth.json" if [[ -f "$auth_file" ]]; then if cp "$auth_file" "$output_file"; then success "✓ Credentials exported to $output_file" info "Keep this file secure - it contains sensitive information" else error_exit "Failed to export credentials" fi else warning "No authentication file to export" fi } # Import credentials (for restore/migration) import_credentials() { local input_file="$1" if [[ -z "$input_file" ]]; then error_exit "Input file required" fi if [[ ! -f "$input_file" ]]; then error_exit "Input file not found: $input_file" fi info "Importing credentials from: $input_file" # Validate JSON format if command -v jq &>/dev/null; then if ! jq empty "$input_file" &>/dev/null; then error_exit "Invalid JSON format in input file" fi fi # Create backup of existing auth file local auth_file="/etc/ostree/auth.json" if [[ -f "$auth_file" ]]; then local backup_file="auth-backup-$(date +%Y%m%d-%H%M%S).json" cp "$auth_file" "$backup_file" info "Created backup: $backup_file" fi # Import new credentials if cp "$input_file" "$auth_file"; then chmod 600 "$auth_file" chown root:root "$auth_file" success "✓ Credentials imported successfully" info "Use 'sync' action to synchronize with podman" else error_exit "Failed to import credentials" fi } # Test registry authentication test_registry_auth() { local registry="$1" if [[ -z "$registry" ]]; then error_exit "Registry name required" fi info "Testing authentication for registry: $registry" # Check if podman is available if ! command -v podman &>/dev/null; then error_exit "podman not available for testing" fi # Try to pull a small test image local test_image="$registry/library/alpine:latest" info "Testing with image: $test_image" if podman pull "$test_image" --quiet; then success "✓ Authentication test passed for $registry" # Clean up test image podman rmi "$test_image" &>/dev/null || true else error_exit "✗ Authentication test failed for $registry" fi } # Rotate credentials rotate_credentials() { local registry="$1" local new_password="$2" if [[ -z "$registry" ]]; then error_exit "Registry name required" fi info "Rotating credentials for registry: $registry" local auth_file="/etc/ostree/auth.json" if [[ ! -f "$auth_file" ]]; then error_exit "No authentication file found" fi # Get current username local username="" if command -v jq &>/dev/null; then username=$(jq -r ".registries[\"$registry\"].username" "$auth_file" 2>/dev/null) if [[ "$username" == "null" || -z "$username" ]]; then error_exit "Registry not found: $registry" fi else error_exit "jq not available - cannot rotate credentials" fi # Get new password if not provided if [[ -z "$new_password" ]]; then echo -n "Enter new password for $username@$registry: " read -rs new_password echo if [[ -z "$new_password" ]]; then error_exit "Password cannot be empty" fi fi # Update password local temp_auth=$(mktemp) jq --arg reg "$registry" \ --arg user "$username" \ --arg pass "$new_password" \ '.registries[$reg] = {"username": $user, "password": $pass}' \ "$auth_file" > "$temp_auth" if [[ $? -eq 0 ]]; then mv "$temp_auth" "$auth_file" success "✓ Credentials rotated for $registry" info "Use 'sync' action to update podman credentials" else error_exit "Failed to rotate credentials" fi } # --- END OF SCRIPTLET: 11-secrets.sh --- # ============================================================================ # Status Reporting and JSON Output # ============================================================================ # bootc status equivalent # Shows system status in human-readable format matching Bazzite style system_status() { info "System Status" # Get current deployment info local current_deployment_file="$PARTICLE_WORKSPACE/current-deployment" local staged_deployment_file="$PARTICLE_WORKSPACE/staged-deployment" local rollback_deployment_file="$PARTICLE_WORKSPACE/rollback-deployment" # Check if this is a Particle-OS system if [[ -f "$current_deployment_file" ]]; then success "✓ Particle-OS system detected" # Read deployment information local current_image="" local current_digest="" local current_version="" local current_timestamp="" if [[ -f "$current_deployment_file" ]]; then current_image=$(jq -r '.image // "unknown"' "$current_deployment_file" 2>/dev/null || echo "unknown") current_digest=$(jq -r '.digest // "unknown"' "$current_deployment_file" 2>/dev/null || echo "unknown") current_version=$(jq -r '.version // "unknown"' "$current_deployment_file" 2>/dev/null || echo "unknown") current_timestamp=$(jq -r '.timestamp // "unknown"' "$current_deployment_file" 2>/dev/null || echo "unknown") fi local staged_image="" local staged_digest="" local staged_version="" local staged_timestamp="" if [[ -f "$staged_deployment_file" ]]; then staged_image=$(jq -r '.image // "none"' "$staged_deployment_file" 2>/dev/null || echo "none") staged_digest=$(jq -r '.digest // "none"' "$staged_deployment_file" 2>/dev/null || echo "none") staged_version=$(jq -r '.version // "none"' "$staged_deployment_file" 2>/dev/null || echo "none") staged_timestamp=$(jq -r '.timestamp // "none"' "$staged_deployment_file" 2>/dev/null || echo "none") fi local rollback_image="" local rollback_digest="" local rollback_version="" local rollback_timestamp="" if [[ -f "$rollback_deployment_file" ]]; then rollback_image=$(jq -r '.image // "none"' "$rollback_deployment_file" 2>/dev/null || echo "none") rollback_digest=$(jq -r '.digest // "none"' "$rollback_deployment_file" 2>/dev/null || echo "none") rollback_version=$(jq -r '.version // "none"' "$rollback_deployment_file" 2>/dev/null || echo "none") rollback_timestamp=$(jq -r '.timestamp // "none"' "$rollback_deployment_file" 2>/dev/null || echo "none") fi # Display status in Bazzite-style format echo "" # Staged image if [[ "$staged_image" != "none" ]]; then echo "Staged image: $staged_image" echo " Digest: $staged_digest" echo " Version: $staged_version ($staged_timestamp)" else echo "Staged image: none" fi echo "" # Booted image (current) if [[ "$current_image" != "unknown" ]]; then echo "● Booted image: $current_image" echo " Digest: $current_digest" echo " Version: $current_version ($current_timestamp)" else echo "● Booted image: unknown" fi echo "" # Rollback image if [[ "$rollback_image" != "none" ]]; then echo "Rollback image: $rollback_image" echo " Digest: $rollback_digest" echo " Version: $rollback_version ($rollback_timestamp)" else echo "Rollback image: none" fi else info "ℹ Not a Particle-OS system" # Check if this is a bootc system if detect_bootc_system; then success "✓ bootc system detected" local bootc_image=$(bootc status --format=json --format-version=1 2>/dev/null | jq -r '.spec.image' 2>/dev/null || echo "null") if [[ "$bootc_image" != "null" ]]; then info "bootc image: $bootc_image" fi fi # Check system type if detect_image_based_system; then success "✓ Image-based system (read-only /usr)" else info "ℹ Traditional system (writable /usr)" fi # Check transient overlay if detect_transient_overlay; then success "✓ Transient overlay active" else info "ℹ No transient overlay" fi # Show OSTree status echo -e "\n=== OSTree Status ===" ostree admin status 2>/dev/null || info "OSTree not available" # Show container images echo -e "\n=== Container Images ===" podman images | grep -E "(particle|bootc)" 2>/dev/null || info "No particle/bootc container images found" fi # Show pending kernel arguments echo -e "\n=== Kernel Arguments ===" local pending_kargs_file="$KARGS_DIR/pending.toml" if [[ -f "$pending_kargs_file" ]]; then info "Pending kernel arguments:" cat "$pending_kargs_file" else info "No pending kernel arguments" fi # Show authentication status echo -e "\n=== Authentication Status ===" local auth_files=("/etc/ostree/auth.json" "/run/ostree/auth.json" "/usr/lib/ostree/auth.json") local auth_found=false for auth_file in "${auth_files[@]}"; do if [[ -f "$auth_file" ]]; then success "✓ Authentication file: $auth_file" auth_found=true fi done if [[ "$auth_found" == "false" ]]; then info "ℹ No authentication files found" fi # Show overlay status echo -e "\n=== Overlay Status ===" usroverlay_status } # bootc status --format=json equivalent # Shows system status in JSON format system_status_json() { info "System Status (JSON format)" # Initialize JSON structure local json_output="{}" # Get deployment information local current_deployment_file="$PARTICLE_WORKSPACE/current-deployment" local staged_deployment_file="$PARTICLE_WORKSPACE/staged-deployment" local rollback_deployment_file="$PARTICLE_WORKSPACE/rollback-deployment" # Read current deployment local current_deployment="{}" if [[ -f "$current_deployment_file" ]]; then current_deployment=$(cat "$current_deployment_file" 2>/dev/null || echo "{}") fi # Read staged deployment local staged_deployment="{}" if [[ -f "$staged_deployment_file" ]]; then staged_deployment=$(cat "$staged_deployment_file" 2>/dev/null || echo "{}") fi # Read rollback deployment local rollback_deployment="{}" if [[ -f "$rollback_deployment_file" ]]; then rollback_deployment=$(cat "$rollback_deployment_file" 2>/dev/null || echo "{}") fi # Add system detection info local is_particle_system=false if [[ -f "$current_deployment_file" ]]; then is_particle_system=true fi local is_bootc_system=false local bootc_image="null" if detect_bootc_system; then is_bootc_system=true bootc_image=$(bootc status --format=json --format-version=1 2>/dev/null | jq -r '.spec.image' 2>/dev/null || echo "null") fi local is_image_based=false if detect_image_based_system; then is_image_based=true fi local has_transient_overlay=false if detect_transient_overlay; then has_transient_overlay=true fi # Get OSTree status local ostree_status="{}" if command -v ostree &> /dev/null; then ostree_status=$(ostree admin status --json 2>/dev/null || echo "{}") fi # Get container images local container_images="[]" if command -v podman &> /dev/null; then container_images=$(podman images --format json | jq -s '.' 2>/dev/null || echo "[]") fi # Get pending kernel arguments local pending_kargs="null" local pending_kargs_file="$KARGS_DIR/pending.toml" if [[ -f "$pending_kargs_file" ]]; then pending_kargs=$(cat "$pending_kargs_file" 2>/dev/null || echo "null") fi # Get authentication status local auth_files=() local auth_locations=("/etc/ostree/auth.json" "/run/ostree/auth.json" "/usr/lib/ostree/auth.json") for auth_file in "${auth_locations[@]}"; do if [[ -f "$auth_file" ]]; then auth_files+=("$auth_file") fi done # Build JSON output json_output=$(cat << EOF { "bootc_alternative": { "version": "$(date '+%y.%m.%d')", "timestamp": "$(date -Iseconds)", "system": { "is_particle_system": $is_particle_system, "is_bootc_system": $is_bootc_system, "bootc_image": $bootc_image, "is_image_based": $is_image_based, "has_transient_overlay": $has_transient_overlay }, "deployments": { "staged": $staged_deployment, "booted": $current_deployment, "rollback": $rollback_deployment }, "ostree": $ostree_status, "containers": $container_images, "kernel_arguments": { "pending": $pending_kargs }, "authentication": { "files": $(printf '%s' "${auth_files[@]}" | jq -R -s -c 'split("\n")[:-1]') }, "overlay": { "active": $has_transient_overlay, "directory": "$USROVERLAY_DIR" } } } EOF ) # Output formatted JSON echo "$json_output" | jq '.' 2>/dev/null || echo "$json_output" } # --- END OF SCRIPTLET: 12-status.sh --- # ============================================================================ # Main Dispatch and Help # ============================================================================ # Show usage information show_usage() { cat << EOF bootc-alternative.sh - Particle-OS alternative to bootc Transactional, in-place operating system updates using OCI/Docker container images Usage: $0 [options] Commands: container-lint Validate container image for bootability build [tag] Build bootable container images deploy [tag] Deploy container as transactional OS update list List available deployments and images rollback Rollback to previous deployment check-updates [tag] Check for container updates status Show system status (human readable) status-json Show system status (JSON format) stage [digest] [version] Stage a new deployment deploy Deploy staged deployment rollback Rollback to previous deployment init-deployment Initialize deployment tracking kargs [args...] Manage kernel arguments kargs list List current kernel arguments kargs add Add kernel argument (applied on next deployment) kargs remove Remove kernel argument from pending list kargs clear Clear all pending kernel arguments secrets [args...] Manage authentication secrets secrets setup [pass] Setup registry authentication (interactive if no pass) secrets sync Synchronize with podman credentials secrets status Check authentication status usroverlay Manage transient writable overlay usroverlay start Start transient overlay for /usr (IMPLEMENTED) usroverlay stop Stop transient overlay (IMPLEMENTED) usroverlay status Check overlay status detect Detect system type and capabilities pkg-check Check package manager compatibility Options: -h, --help Print help (see more with '--help') -V, --version Print version Examples: $0 container-lint particle-os:latest $0 build Containerfile particle-os v1.0 $0 deploy particle-os:latest $0 list $0 rollback $0 check-updates particle-os:latest $0 status-json $0 stage particle-os:latest $0 deploy $0 rollback $0 init-deployment $0 kargs list $0 kargs add "console=ttyS0,115200" $0 secrets setup quay.io username $0 secrets sync $0 usroverlay start $0 usroverlay status $0 detect $0 pkg-check $0 --version This script provides bootc functionality using native ostree commands for Particle-OS. Based on actual bootc source code and documentation from https://github.com/bootc-dev/bootc Image requirements: https://bootc-dev.github.io/bootc/bootc-images.html Building guidance: https://bootc-dev.github.io/bootc/building/guidance.html Package manager integration: https://bootc-dev.github.io/bootc/package-managers.html IMPROVEMENTS: - ✅ usroverlay: Full overlayfs implementation with start/stop/status - ✅ kargs: Pending kernel arguments with deployment integration - ✅ secrets: Secure interactive password input - ✅ status-json: Dynamic values and proper deployment tracking - ✅ check-updates: Proper digest comparison using skopeo - ✅ rollback: Improved deployment parsing logic - ✅ deployment-tracking: Bazzite-style staged/booted/rollback deployment management - ✅ version-command: Professional version output with compilation date EOF } # Main function main() { # Parse command line arguments first to handle version command if [[ $# -eq 0 ]]; then show_usage exit 1 fi local command="${1:-}" shift # Handle version command before root checks case "$command" in "version"|"-V"|"--version") show_version ;; esac # Check if running as root (skip for version command) check_root # Check dependencies check_dependencies # Initialize directories init_directories case "$command" in "container-lint") if ! validate_args "$@" 1 1 "container-lint"; then error_exit "Container image name required" fi local image_name="${1:-}" if ! validate_container_image "$image_name"; then exit 1 fi container_lint "$image_name" ;; "build") if ! validate_args "$@" 2 3 "build"; then error_exit "Dockerfile and image name required" fi local dockerfile="${1:-}" local image_name="${2:-}" local tag="${3:-latest}" if ! validate_file_path "$dockerfile" "dockerfile"; then exit 1 fi if ! validate_container_image "$image_name"; then exit 1 fi build_container "$dockerfile" "$image_name" "$tag" ;; "deploy") if ! validate_args "$@" 1 2 "deploy"; then error_exit "Container image name required" fi local image_name="${1:-}" local tag="${2:-latest}" if ! validate_container_image "$image_name"; then exit 1 fi deploy_container "$image_name" "$tag" ;; "list") list_deployments ;; "rollback") rollback_deployment ;; "check-updates") if ! validate_args "$@" 1 2 "check-updates"; then error_exit "Container image name required" fi local image_name="${1:-}" local tag="${2:-latest}" if ! validate_container_image "$image_name"; then exit 1 fi check_updates "$image_name" "$tag" ;; "status") system_status ;; "status-json") system_status_json ;; "stage") if ! validate_args "$@" 1 3 "stage"; then error_exit "Image name required for staging" fi local image_name="${1:-}" local digest="${2:-}" local version="${3:-}" if ! validate_container_image "$image_name"; then exit 1 fi stage_deployment "$image_name" "$digest" "$version" ;; "deploy") deploy_staged ;; "rollback") rollback_deployment ;; "init-deployment") init_deployment_tracking ;; "kargs") if ! validate_args "$@" 1 10 "kargs"; then error_exit "kargs action required (list, add, remove, clear)" fi manage_kernel_args "$@" ;; "secrets") if ! validate_args "$@" 1 10 "secrets"; then error_exit "secrets action required (setup, sync, status)" fi manage_secrets "$@" ;; "usroverlay") if ! validate_args "$@" 1 10 "usroverlay"; then error_exit "usroverlay action required (start, stop, status)" fi usroverlay "$@" ;; "detect") info "Detecting system type and capabilities" echo "=== System Detection ===" if detect_bootc_system; then success "✓ bootc system detected" local bootc_image=$(bootc status --format=json --format-version=1 2>/dev/null | jq -r '.spec.image' 2>/dev/null || echo "null") info "bootc image: $bootc_image" else info "ℹ Not a bootc system" fi if detect_image_based_system; then success "✓ Image-based system detected (read-only /usr)" else info "ℹ Traditional system detected (writable /usr)" fi if detect_transient_overlay; then success "✓ Transient overlay detected" else info "ℹ No transient overlay active" fi ;; "pkg-check") package_manager_check ;; "help"|"-h"|"--help") show_usage ;; *) error_exit "Unknown command: $command" ;; esac } # Run main function with all arguments main "$@" # --- END OF SCRIPTLET: 99-main.sh --- # ============================================================================ # Embedded Configuration Files # ============================================================================ # Embedded configuration: bootc-settings # File size: 662 declare -A BOOTC-SETTINGS_CONFIG=$(cat << 'EOF' { "default_registry": "quay.io/particle-os", "default_image": "ubuntu-ublue:latest", "ostree_repo_path": "/ostree/repo", "log_file": "/var/log/bootc-alternative.log", "usroverlay_dir": "/var/lib/bootc-alternative/usroverlay", "kargs_dir": "/var/lib/bootc-alternative/kargs", "features": { "container_lint": true, "usroverlay": true, "kernel_args": true, "secrets_management": true, "status_json": true }, "validation": { "check_dependencies": true, "validate_json": true, "syntax_check": true }, "logging": { "level": "info", "file_output": true, "console_output": true, "colors": true } } EOF ) # Embedded configuration: container-validation # File size: 675 declare -A CONTAINER-VALIDATION_CONFIG=$(cat << 'EOF' { "required_files": [ "/usr/lib/systemd/systemd", "/usr/lib/modules", "/etc/ostree" ], "required_labels": [ "containers.bootc" ], "kernel_requirements": { "vmlinuz_required": true, "initramfs_required": true, "modules_directory": "/usr/lib/modules" }, "filesystem_structure": { "boot_should_be_empty": true, "usr_readonly": true, "var_writable": true }, "validation_levels": { "strict": { "all_checks": true, "fail_on_warning": true }, "normal": { "core_checks": true, "fail_on_error": true }, "permissive": { "core_checks": true, "warn_only": true } } } EOF ) # ============================================================================ # External Configuration Loading (Future Enhancement) # ============================================================================ # Function to load configuration from external files # Usage: load_config_from_file "config-name" load_config_from_file() { local config_name="$1" local config_file="/etc/bootc-alternative/config/${config_name}.json" if [[ -f "$config_file" ]]; then jq -r '.' "$config_file" else log_error "Configuration file not found: $config_file" "bootc-alternative" exit 1 fi }