- Complete Particle-OS rebranding from uBlue-OS - Professional installation system with standardized paths - Self-initialization system with --init and --reset commands - Enhanced error messages and dependency checking - Comprehensive testing infrastructure - All source scriptlets updated with runtime improvements - Clean codebase with redundant files moved to archive - Complete documentation suite
4204 lines
128 KiB
Bash
4204 lines
128 KiB
Bash
#!/bin/bash
|
||
|
||
################################################################################################################
|
||
# #
|
||
# WARNING: This file is automatically generated #
|
||
# DO NOT modify this file directly as it will be overwritten #
|
||
# #
|
||
# Ubuntu uBlue BootC Alternative #
|
||
# Generated on: 2025-07-10 22:47:25 #
|
||
# #
|
||
################################################################################################################
|
||
|
||
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.10
|
||
# 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
|
||
# ============================================================================
|
||
|
||
|
||
# --- 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
|
||
}
|
||
|
||
# --- 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
|
||
|
||
# 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
|
||
|
||
# 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
|
||
system_status() {
|
||
info "System Status"
|
||
|
||
echo "=== BootC Alternative Status ==="
|
||
|
||
# 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
|
||
else
|
||
info "ℹ Not a bootc system"
|
||
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
|
||
|
||
# Show container images
|
||
echo -e "\n=== Container Images ==="
|
||
podman images | grep -E "(ublue|bootc)" || info "No ublue/bootc container images found"
|
||
|
||
# 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="{}"
|
||
|
||
# Add system detection info
|
||
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_bootc_system": $is_bootc_system,
|
||
"bootc_image": $bootc_image,
|
||
"is_image_based": $is_image_based,
|
||
"has_transient_overlay": $has_transient_overlay
|
||
},
|
||
"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 - Ubuntu ublue 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)
|
||
|
||
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
|
||
|
||
Examples:
|
||
$0 container-lint ubuntu-ublue:latest
|
||
$0 build Containerfile ubuntu-ublue v1.0
|
||
$0 deploy ubuntu-ublue:latest
|
||
$0 list
|
||
$0 rollback
|
||
$0 check-updates ubuntu-ublue:latest
|
||
$0 status-json
|
||
$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
|
||
|
||
This script provides bootc functionality using native ostree commands for Ubuntu ublue.
|
||
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
|
||
|
||
EOF
|
||
}
|
||
|
||
# Main function
|
||
main() {
|
||
# Check if running as root
|
||
check_root
|
||
|
||
# Check dependencies
|
||
check_dependencies
|
||
|
||
# Initialize directories
|
||
init_directories
|
||
|
||
# Parse command line arguments
|
||
if [[ $# -eq 0 ]]; then
|
||
show_usage
|
||
exit 1
|
||
fi
|
||
|
||
local command="$1"
|
||
shift
|
||
|
||
case "$command" in
|
||
"container-lint")
|
||
if [[ $# -eq 0 ]]; then
|
||
error_exit "Container image name required"
|
||
fi
|
||
container_lint "$1"
|
||
;;
|
||
"build")
|
||
if [[ $# -lt 2 ]]; then
|
||
error_exit "Dockerfile and image name required"
|
||
fi
|
||
build_container "$1" "$2" "${3:-latest}"
|
||
;;
|
||
"deploy")
|
||
if [[ $# -eq 0 ]]; then
|
||
error_exit "Container image name required"
|
||
fi
|
||
deploy_container "$1" "${2:-latest}"
|
||
;;
|
||
"list")
|
||
list_deployments
|
||
;;
|
||
"rollback")
|
||
rollback_deployment
|
||
;;
|
||
"check-updates")
|
||
if [[ $# -eq 0 ]]; then
|
||
error_exit "Container image name required"
|
||
fi
|
||
check_updates "$1" "${2:-latest}"
|
||
;;
|
||
"status")
|
||
system_status
|
||
;;
|
||
"status-json")
|
||
system_status_json
|
||
;;
|
||
"kargs")
|
||
if [[ $# -eq 0 ]]; then
|
||
error_exit "kargs action required (list, add, remove, clear)"
|
||
fi
|
||
manage_kernel_args "$@"
|
||
;;
|
||
"secrets")
|
||
if [[ $# -eq 0 ]]; then
|
||
error_exit "secrets action required (setup, sync, status)"
|
||
fi
|
||
manage_secrets "$@"
|
||
;;
|
||
"usroverlay")
|
||
if [[ $# -eq 0 ]]; 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
|
||
}
|
||
|