particle-os-tools/bootc-alternative.sh
robojerk 74c7bede5f Initial commit: Particle-OS tools repository
- 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
2025-07-11 21:14:33 -07:00

4204 lines
128 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

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

#!/bin/bash
################################################################################################################
# #
# WARNING: This file is automatically generated #
# DO NOT modify this file directly as it will be overwritten #
# #
# Ubuntu uBlue BootC Alternative #
# Generated on: 2025-07-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
}