particle-os-tools/bootc-alternative.sh
2025-07-14 01:09:07 -07:00

5448 lines
166 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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
}