5448 lines
166 KiB
Bash
5448 lines
166 KiB
Bash
#!/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<to_remove; i++)); do
|
||
local backup_ref="${backups[i]}"
|
||
info "Removing old backup: $backup_ref"
|
||
if ostree refs --repo="$OSTREE_REPO" --delete "$backup_ref"; then
|
||
((removed_count++))
|
||
else
|
||
warning "Failed to remove backup: $backup_ref"
|
||
fi
|
||
done
|
||
|
||
success "Cleaned $removed_count old backups"
|
||
info "Kept $keep_count most recent backups"
|
||
}
|
||
|
||
# --- END OF SCRIPTLET: 07-reinstall.sh ---
|
||
|
||
# ============================================================================
|
||
# Systemd Integration
|
||
# ============================================================================
|
||
# Systemd integration functions
|
||
# systemd integration and service management for bootc
|
||
|
||
# Systemd operations
|
||
systemd_operations() {
|
||
local action="$1"
|
||
shift
|
||
|
||
case "$action" in
|
||
"enable")
|
||
enable_systemd_services "$@"
|
||
;;
|
||
"disable")
|
||
disable_systemd_services "$@"
|
||
;;
|
||
"start")
|
||
start_systemd_services "$@"
|
||
;;
|
||
"stop")
|
||
stop_systemd_services "$@"
|
||
;;
|
||
"restart")
|
||
restart_systemd_services "$@"
|
||
;;
|
||
"status")
|
||
check_systemd_status "$@"
|
||
;;
|
||
"reload")
|
||
reload_systemd_units "$@"
|
||
;;
|
||
"mask")
|
||
mask_systemd_units "$@"
|
||
;;
|
||
"unmask")
|
||
unmask_systemd_units "$@"
|
||
;;
|
||
"preset")
|
||
preset_systemd_units "$@"
|
||
;;
|
||
*)
|
||
error_exit "Unknown systemd action: $action"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# Enable systemd services
|
||
enable_systemd_services() {
|
||
local services=("$@")
|
||
|
||
if [[ ${#services[@]} -eq 0 ]]; then
|
||
error_exit "No services specified for enabling"
|
||
fi
|
||
|
||
info "Enabling systemd services: ${services[*]}"
|
||
|
||
local failed_services=()
|
||
for service in "${services[@]}"; do
|
||
if systemctl enable "$service"; then
|
||
success "✓ Enabled service: $service"
|
||
else
|
||
warning "✗ Failed to enable service: $service"
|
||
failed_services+=("$service")
|
||
fi
|
||
done
|
||
|
||
if [[ ${#failed_services[@]} -gt 0 ]]; then
|
||
warning "Failed to enable ${#failed_services[@]} services: ${failed_services[*]}"
|
||
return 1
|
||
fi
|
||
|
||
success "All services enabled successfully"
|
||
}
|
||
|
||
# Disable systemd services
|
||
disable_systemd_services() {
|
||
local services=("$@")
|
||
|
||
if [[ ${#services[@]} -eq 0 ]]; then
|
||
error_exit "No services specified for disabling"
|
||
fi
|
||
|
||
info "Disabling systemd services: ${services[*]}"
|
||
|
||
local failed_services=()
|
||
for service in "${services[@]}"; do
|
||
if systemctl disable "$service"; then
|
||
success "✓ Disabled service: $service"
|
||
else
|
||
warning "✗ Failed to disable service: $service"
|
||
failed_services+=("$service")
|
||
fi
|
||
done
|
||
|
||
if [[ ${#failed_services[@]} -gt 0 ]]; then
|
||
warning "Failed to disable ${#failed_services[@]} services: ${failed_services[*]}"
|
||
return 1
|
||
fi
|
||
|
||
success "All services disabled successfully"
|
||
}
|
||
|
||
# Start systemd services
|
||
start_systemd_services() {
|
||
local services=("$@")
|
||
|
||
if [[ ${#services[@]} -eq 0 ]]; then
|
||
error_exit "No services specified for starting"
|
||
fi
|
||
|
||
info "Starting systemd services: ${services[*]}"
|
||
|
||
local failed_services=()
|
||
for service in "${services[@]}"; do
|
||
if systemctl start "$service"; then
|
||
success "✓ Started service: $service"
|
||
else
|
||
warning "✗ Failed to start service: $service"
|
||
failed_services+=("$service")
|
||
fi
|
||
done
|
||
|
||
if [[ ${#failed_services[@]} -gt 0 ]]; then
|
||
warning "Failed to start ${#failed_services[@]} services: ${failed_services[*]}"
|
||
return 1
|
||
fi
|
||
|
||
success "All services started successfully"
|
||
}
|
||
|
||
# Stop systemd services
|
||
stop_systemd_services() {
|
||
local services=("$@")
|
||
|
||
if [[ ${#services[@]} -eq 0 ]]; then
|
||
error_exit "No services specified for stopping"
|
||
fi
|
||
|
||
info "Stopping systemd services: ${services[*]}"
|
||
|
||
local failed_services=()
|
||
for service in "${services[@]}"; do
|
||
if systemctl stop "$service"; then
|
||
success "✓ Stopped service: $service"
|
||
else
|
||
warning "✗ Failed to stop service: $service"
|
||
failed_services+=("$service")
|
||
fi
|
||
done
|
||
|
||
if [[ ${#failed_services[@]} -gt 0 ]]; then
|
||
warning "Failed to stop ${#failed_services[@]} services: ${failed_services[*]}"
|
||
return 1
|
||
fi
|
||
|
||
success "All services stopped successfully"
|
||
}
|
||
|
||
# Restart systemd services
|
||
restart_systemd_services() {
|
||
local services=("$@")
|
||
|
||
if [[ ${#services[@]} -eq 0 ]]; then
|
||
error_exit "No services specified for restarting"
|
||
fi
|
||
|
||
info "Restarting systemd services: ${services[*]}"
|
||
|
||
local failed_services=()
|
||
for service in "${services[@]}"; do
|
||
if systemctl restart "$service"; then
|
||
success "✓ Restarted service: $service"
|
||
else
|
||
warning "✗ Failed to restart service: $service"
|
||
failed_services+=("$service")
|
||
fi
|
||
done
|
||
|
||
if [[ ${#failed_services[@]} -gt 0 ]]; then
|
||
warning "Failed to restart ${#failed_services[@]} services: ${failed_services[*]}"
|
||
return 1
|
||
fi
|
||
|
||
success "All services restarted successfully"
|
||
}
|
||
|
||
# Check systemd service status
|
||
check_systemd_status() {
|
||
local services=("$@")
|
||
|
||
if [[ ${#services[@]} -eq 0 ]]; then
|
||
# Show overall systemd status
|
||
info "Checking overall systemd status"
|
||
systemctl status --no-pager
|
||
return 0
|
||
fi
|
||
|
||
info "Checking status of services: ${services[*]}"
|
||
|
||
for service in "${services[@]}"; do
|
||
echo "=== Status: $service ==="
|
||
if systemctl is-active "$service" &>/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 <command> [options]
|
||
|
||
Commands:
|
||
container-lint <image> Validate container image for bootability
|
||
build <dockerfile> <image> [tag] Build bootable container images
|
||
deploy <image> [tag] Deploy container as transactional OS update
|
||
list List available deployments and images
|
||
rollback Rollback to previous deployment
|
||
check-updates <image> [tag] Check for container updates
|
||
|
||
status Show system status (human readable)
|
||
status-json Show system status (JSON format)
|
||
|
||
stage <image> [digest] [version] Stage a new deployment
|
||
deploy Deploy staged deployment
|
||
rollback Rollback to previous deployment
|
||
init-deployment Initialize deployment tracking
|
||
|
||
kargs <action> [args...] Manage kernel arguments
|
||
kargs list List current kernel arguments
|
||
kargs add <argument> Add kernel argument (applied on next deployment)
|
||
kargs remove <argument> Remove kernel argument from pending list
|
||
kargs clear Clear all pending kernel arguments
|
||
|
||
secrets <action> [args...] Manage authentication secrets
|
||
secrets setup <reg> <user> [pass] Setup registry authentication (interactive if no pass)
|
||
secrets sync Synchronize with podman credentials
|
||
secrets status Check authentication status
|
||
|
||
usroverlay <action> 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
|
||
}
|
||
|