- 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
1209 lines
42 KiB
Bash
1209 lines
42 KiB
Bash
#!/bin/bash
|
|
|
|
################################################################################################################
|
|
# #
|
|
# WARNING: This file is automatically generated #
|
|
# DO NOT modify this file directly as it will be overwritten #
|
|
# #
|
|
# Ubuntu uBlue ComposeFS Alternative #
|
|
# Generated on: 2025-07-10 22:47:36 #
|
|
# #
|
|
################################################################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# Ubuntu uBlue ComposeFS Alternative - Self-contained version
|
|
# This script contains all components merged into a single file
|
|
# Based on composefs design principles: https://github.com/containers/composefs
|
|
|
|
|
|
# Version: 25.07.10
|
|
# Ubuntu uBlue ComposeFS Alternative
|
|
# Content-addressable layered filesystem for Ubuntu
|
|
|
|
|
|
# Source Ubuntu uBlue configuration (if available)
|
|
if [[ -f "/usr/local/etc/particle-config.sh" ]]; then
|
|
source "/usr/local/etc/particle-config.sh"
|
|
log_info "Loaded Ubuntu uBlue configuration" "composefs-alternative"
|
|
else
|
|
# Define logging functions if not available
|
|
log_info() {
|
|
local message="$1"
|
|
local script_name="${2:-composefs-alternative}"
|
|
echo "[INFO] [$script_name] $message"
|
|
}
|
|
log_warning() {
|
|
local message="$1"
|
|
local script_name="${2:-composefs-alternative}"
|
|
echo "[WARNING] [$script_name] $message" >&2
|
|
}
|
|
log_error() {
|
|
local message="$1"
|
|
local script_name="${2:-composefs-alternative}"
|
|
echo "[ERROR] [$script_name] $message" >&2
|
|
}
|
|
log_debug() {
|
|
local message="$1"
|
|
local script_name="${2:-composefs-alternative}"
|
|
echo "[DEBUG] [$script_name] $message"
|
|
}
|
|
log_success() {
|
|
local message="$1"
|
|
local script_name="${2:-composefs-alternative}"
|
|
echo "[SUCCESS] [$script_name] $message"
|
|
}
|
|
log_warning "Ubuntu uBlue configuration not found, using defaults" "composefs-alternative"
|
|
fi
|
|
|
|
|
|
# ============================================================================
|
|
# Header and Shared Functions
|
|
# ============================================================================
|
|
|
|
|
|
# --- END OF SCRIPTLET: 00-header.sh ---
|
|
|
|
# ============================================================================
|
|
# Dependency Checking and Validation
|
|
# ============================================================================
|
|
# Check dependencies for Ubuntu uBlue ComposeFS Alternative
|
|
check_dependencies() {
|
|
local deps=(
|
|
"mount" # Filesystem mounting
|
|
"umount" # Filesystem unmounting
|
|
"findmnt" # Mount point detection
|
|
"losetup" # Loop device management
|
|
"sha256sum" # Hash generation
|
|
"jq" # JSON processing
|
|
"tar" # Archive operations
|
|
"mksquashfs" # SquashFS creation
|
|
"unsquashfs" # SquashFS extraction
|
|
"nproc" # CPU core detection
|
|
"du" # Disk usage
|
|
"stat" # File status
|
|
"modprobe" # Kernel module loading
|
|
"mountpoint" # Mount point checking
|
|
)
|
|
local missing=()
|
|
|
|
for dep in "${deps[@]}"; do
|
|
if ! command -v "$dep" &> /dev/null; then
|
|
missing+=("$dep")
|
|
fi
|
|
done
|
|
|
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
log_error "Missing required dependencies: ${missing[*]}" "composefs-alternative"
|
|
log_error "Please install the missing packages (squashfs-tools, jq, coreutils, util-linux)" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "All dependencies are available" "composefs-alternative"
|
|
}
|
|
|
|
# Check kernel modules
|
|
check_kernel_modules() {
|
|
local modules=("squashfs" "overlay")
|
|
local missing_modules=()
|
|
|
|
for module in "${modules[@]}"; do
|
|
if ! modprobe -n "$module" >/dev/null 2>&1; then
|
|
missing_modules+=("$module")
|
|
fi
|
|
done
|
|
|
|
if [[ ${#missing_modules[@]} -gt 0 ]]; then
|
|
log_warning "Missing kernel modules: ${missing_modules[*]}" "composefs-alternative"
|
|
log_warning "Some features may not work correctly" "composefs-alternative"
|
|
return 1
|
|
fi
|
|
|
|
log_info "All required kernel modules are available" "composefs-alternative"
|
|
return 0
|
|
}
|
|
|
|
# --- END OF SCRIPTLET: 01-dependencies.sh ---
|
|
|
|
# ============================================================================
|
|
# Content Hash Generation
|
|
# ============================================================================
|
|
# PERFORMANCE: Optimized content hash generation with true parallel processing
|
|
generate_content_hash() {
|
|
local source_dir="$1"
|
|
local temp_file
|
|
temp_file=$(mktemp)
|
|
|
|
# Add to cleanup list for robust file management
|
|
CLEANUP_FILES+=("$temp_file")
|
|
|
|
log_info "Generating content hash for: $source_dir" "composefs-alternative"
|
|
|
|
# Count total files for progress indication
|
|
local total_files
|
|
total_files=$(find "$source_dir" -type f | wc -l)
|
|
log_info "Found $total_files files to process" "composefs-alternative"
|
|
|
|
# Create temporary manifest file
|
|
local manifest_file
|
|
manifest_file=$(mktemp)
|
|
CLEANUP_FILES+=("$manifest_file")
|
|
|
|
# Use true parallel processing for better performance on large directories
|
|
# Process files in parallel using xargs with proper error handling
|
|
local parallel_jobs
|
|
parallel_jobs=$(nproc 2>/dev/null || echo 4) # Use number of CPU cores, fallback to 4
|
|
|
|
# Limit parallel jobs to prevent system overload
|
|
if [[ $parallel_jobs -gt 8 ]]; then
|
|
parallel_jobs=8
|
|
fi
|
|
|
|
log_info "Using $parallel_jobs parallel jobs for hash generation" "composefs-alternative"
|
|
|
|
# Create a temporary script for parallel processing
|
|
local parallel_script
|
|
parallel_script=$(mktemp)
|
|
CLEANUP_FILES+=("$parallel_script")
|
|
|
|
# Write the parallel processing script with error handling
|
|
cat > "$parallel_script" << 'EOF'
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
source_dir="$1"
|
|
file="$2"
|
|
manifest_file="$3"
|
|
|
|
# Validate inputs
|
|
if [[ ! -f "$file" ]]; then
|
|
echo "ERROR: File not found: $file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -r "$file" ]]; then
|
|
echo "ERROR: File not readable: $file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Calculate hash for this file
|
|
file_hash=$(sha256sum "$file" | cut -d' ' -f1)
|
|
|
|
# Get relative path for consistent hashing
|
|
relative_path=$(realpath --relative-to="$source_dir" "$file")
|
|
|
|
# Add to manifest (with lock to prevent race conditions)
|
|
echo "$file_hash $relative_path" >> "$manifest_file"
|
|
EOF
|
|
|
|
chmod +x "$parallel_script"
|
|
|
|
# Process files in parallel with progress indication
|
|
local processed=0
|
|
local batch_size=100
|
|
|
|
# Use find with -print0 and xargs for safe parallel processing
|
|
# Fallback to sequential processing if xargs fails
|
|
if find "$source_dir" -type f -print0 | xargs -0 -P"$parallel_jobs" -I {} bash "$parallel_script" "$source_dir" {} "$manifest_file" 2>/dev/null; then
|
|
log_info "Parallel hash generation completed successfully" "composefs-alternative"
|
|
else
|
|
log_warning "Parallel processing failed, falling back to sequential processing" "composefs-alternative"
|
|
|
|
# Fallback to sequential processing
|
|
find "$source_dir" -type f | while IFS= read -r file; do
|
|
# Calculate hash for this file
|
|
file_hash=$(sha256sum "$file" | cut -d' ' -f1)
|
|
|
|
# Get relative path for consistent hashing
|
|
relative_path=$(realpath --relative-to="$source_dir" "$file")
|
|
|
|
# Add to manifest
|
|
echo "$file_hash $relative_path" >> "$manifest_file"
|
|
done
|
|
|
|
log_info "Sequential hash generation completed successfully" "composefs-alternative"
|
|
fi
|
|
|
|
# Sort manifest for consistent hashing
|
|
sort "$manifest_file" > "$temp_file"
|
|
|
|
# Hash the sorted manifest to create content-addressable ID
|
|
local content_hash
|
|
content_hash=$(sha256sum "$temp_file" | cut -d' ' -f1)
|
|
|
|
log_info "Content hash generated: $content_hash" "composefs-alternative"
|
|
|
|
# Clean up immediately (though trap will handle it too)
|
|
rm -f "$temp_file" "$manifest_file" "$parallel_script"
|
|
|
|
echo "$content_hash"
|
|
}
|
|
|
|
# --- END OF SCRIPTLET: 02-hash.sh ---
|
|
|
|
# ============================================================================
|
|
# Layer Management
|
|
# ============================================================================
|
|
# Layer management functions for Ubuntu uBlue ComposeFS Alternative
|
|
|
|
# Check if layer already exists (deduplication)
|
|
layer_exists() {
|
|
local layer_id="$1"
|
|
local layer_dir="$COMPOSEFS_DIR/layers/$layer_id"
|
|
|
|
if [[ -d "$layer_dir" ]] && [[ -f "$layer_dir/metadata.json" ]]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Create a layer from a directory with content-addressable ID and progress indication
|
|
create_layer() {
|
|
local source_dir="$1"
|
|
|
|
# SECURITY: Validate source directory
|
|
source_dir=$(validate_path "$source_dir" "source_dir")
|
|
|
|
# Generate content-addressable layer ID
|
|
local layer_id
|
|
layer_id=$(generate_content_hash "$source_dir")
|
|
local layer_dir="$COMPOSEFS_DIR/layers/$layer_id"
|
|
|
|
# Check if layer already exists (deduplication)
|
|
if layer_exists "$layer_id"; then
|
|
log_info "Layer already exists: $layer_id (deduplication)" "composefs-alternative"
|
|
echo "$layer_id"
|
|
return 0
|
|
fi
|
|
|
|
log_info "Creating new layer: $layer_id from $source_dir" "composefs-alternative"
|
|
|
|
# Create layer directory
|
|
mkdir -p "$layer_dir"
|
|
|
|
# Create squashfs image for immutable layer with progress indication
|
|
local squashfs_file="$layer_dir/data.squashfs"
|
|
|
|
# Calculate source directory size for better progress indication
|
|
local source_size
|
|
source_size=$(du -sb "$source_dir" | cut -f1)
|
|
local source_size_mb=$((source_size / 1024 / 1024))
|
|
|
|
log_info "Creating squashfs image from $source_size_mb MB source directory (this may take a while)..." "composefs-alternative"
|
|
|
|
# Check available disk space before creating squashfs
|
|
local available_space
|
|
available_space=$(get_available_space "$(dirname "$squashfs_file")")
|
|
if [[ $available_space -lt $((source_size_mb / 2)) ]]; then
|
|
log_error "Insufficient disk space: ${available_space}MB available, need at least $((source_size_mb / 2))MB for squashfs creation" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
|
|
# Use configurable compression (default to xz if not set)
|
|
local compression="${UBLUE_SQUASHFS_COMPRESSION:-xz}"
|
|
|
|
# Use progress indication for mksquashfs with compression info
|
|
if mksquashfs "$source_dir" "$squashfs_file" -comp "$compression" -progress -quiet; then
|
|
local squashfs_size
|
|
squashfs_size=$(stat -c%s "$squashfs_file")
|
|
local squashfs_size_mb=$((squashfs_size / 1024 / 1024))
|
|
local compression_ratio=$((source_size_mb * 100 / squashfs_size_mb))
|
|
|
|
log_info "Squashfs image created successfully: ${squashfs_size_mb}MB (${compression_ratio}% of original size)" "composefs-alternative"
|
|
else
|
|
log_error "Failed to create squashfs image for layer: $layer_id (check available disk space and squashfs-tools installation)" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
|
|
# Create layer metadata
|
|
local layer_metadata="$layer_dir/metadata.json"
|
|
cat > "$layer_metadata" <<EOF
|
|
{
|
|
"id": "$layer_id",
|
|
"created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"size": $(stat -c%s "$squashfs_file"),
|
|
"digest": "$(sha256sum "$squashfs_file" | cut -d' ' -f1)",
|
|
"type": "squashfs",
|
|
"source": "$source_dir"
|
|
}
|
|
EOF
|
|
|
|
log_info "Layer created: $layer_id" "composefs-alternative"
|
|
echo "$layer_id"
|
|
}
|
|
|
|
# Calculate actual disk space used by layers (accounting for deduplication)
|
|
calculate_layer_disk_usage() {
|
|
local layer_ids=("$@")
|
|
local total_size=0
|
|
local unique_layers=()
|
|
|
|
# Get unique layer IDs (deduplication)
|
|
for layer_id in "${layer_ids[@]}"; do
|
|
local is_duplicate=false
|
|
for unique_id in "${unique_layers[@]}"; do
|
|
if [[ "$unique_id" == "$layer_id" ]]; then
|
|
is_duplicate=true
|
|
break
|
|
fi
|
|
done
|
|
if [[ "$is_duplicate" == false ]]; then
|
|
unique_layers+=("$layer_id")
|
|
fi
|
|
done
|
|
|
|
# Calculate size of unique layers only
|
|
for layer_id in "${unique_layers[@]}"; do
|
|
local layer_dir="$COMPOSEFS_DIR/layers/$layer_id"
|
|
local layer_metadata="$layer_dir/metadata.json"
|
|
if [[ -f "$layer_metadata" ]]; then
|
|
local layer_size
|
|
layer_size=$(jq -r '.size' "$layer_metadata")
|
|
total_size=$((total_size + layer_size))
|
|
fi
|
|
done
|
|
|
|
echo "$total_size"
|
|
}
|
|
|
|
# Mount a layer using squashfs for immutability
|
|
mount_layer() {
|
|
local layer_id="$1"
|
|
local layer_dir="$COMPOSEFS_DIR/layers/$layer_id"
|
|
local squashfs_file="$layer_dir/data.squashfs"
|
|
local mount_point="$layer_dir/mount"
|
|
|
|
# Create mount point
|
|
mkdir -p "$mount_point"
|
|
|
|
# Check if already mounted
|
|
if mountpoint -q "$mount_point" 2>/dev/null; then
|
|
log_info "Layer already mounted: $layer_id" "composefs-alternative"
|
|
return 0
|
|
fi
|
|
|
|
# Mount squashfs as read-only
|
|
if mount -t squashfs -o ro "$squashfs_file" "$mount_point"; then
|
|
log_info "Layer mounted: $layer_id at $mount_point" "composefs-alternative"
|
|
# Add to cleanup list
|
|
CLEANUP_MOUNTS+=("$mount_point")
|
|
else
|
|
log_error "Failed to mount layer: $layer_id (check if squashfs module is loaded)" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# --- END OF SCRIPTLET: 03-layers.sh ---
|
|
|
|
# ============================================================================
|
|
# Image Management
|
|
# ============================================================================
|
|
# Image management functions for Ubuntu uBlue ComposeFS Alternative
|
|
|
|
# Create a composefs image from multiple directories (multi-layer support)
|
|
create_image() {
|
|
local image_name="$1"
|
|
shift
|
|
local source_dirs=("$@")
|
|
|
|
# SECURITY: Validate image name
|
|
image_name=$(validate_image_name "$image_name")
|
|
|
|
if [[ ${#source_dirs[@]} -eq 0 ]]; then
|
|
log_error "No source directories provided" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Creating composefs image: $image_name with ${#source_dirs[@]} layers" "composefs-alternative"
|
|
|
|
# SECURITY: Validate all source directories
|
|
for source_dir in "${source_dirs[@]}"; do
|
|
source_dir=$(validate_path "$source_dir" "source_dir")
|
|
if [[ ! -d "$source_dir" ]]; then
|
|
log_error "Source directory does not exist: $source_dir (check path and permissions)" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
if [[ ! -r "$source_dir" ]]; then
|
|
log_error "Source directory is not readable: $source_dir (check permissions)" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
local image_dir="$COMPOSEFS_DIR/images/$image_name"
|
|
|
|
# Check if image already exists
|
|
if [[ -d "$image_dir" ]]; then
|
|
log_error "Image already exists: $image_name. Use 'remove' command first if you want to recreate it." "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
|
|
# Create image directory
|
|
mkdir -p "$image_dir"
|
|
|
|
# Create layers from source directories with progress indication
|
|
local layer_ids=()
|
|
local layer_count=0
|
|
|
|
for source_dir in "${source_dirs[@]}"; do
|
|
layer_count=$((layer_count + 1))
|
|
log_info "Creating layer $layer_count/${#source_dirs[@]}: $source_dir" "composefs-alternative"
|
|
|
|
local layer_id
|
|
layer_id=$(create_layer "$source_dir")
|
|
layer_ids+=("$layer_id")
|
|
done
|
|
|
|
# Create metadata
|
|
local metadata_file="$image_dir/metadata.json"
|
|
cat > "$metadata_file" <<EOF
|
|
{
|
|
"version": "1.0",
|
|
"created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"layers": $(printf '%s\n' "${layer_ids[@]}" | jq -R . | jq -s .),
|
|
"sources": $(printf '%s\n' "${source_dirs[@]}" | jq -R . | jq -s .)
|
|
}
|
|
EOF
|
|
|
|
log_info "Composefs image created: $image_name" "composefs-alternative"
|
|
log_info "Layers: ${layer_ids[*]}" "composefs-alternative"
|
|
log_info "Metadata: $metadata_file" "composefs-alternative"
|
|
}
|
|
|
|
# Mount a composefs image
|
|
mount_image() {
|
|
local image_name="$1"
|
|
local mount_point="$2"
|
|
|
|
# SECURITY: Validate inputs
|
|
image_name=$(validate_image_name "$image_name")
|
|
mount_point=$(validate_path "$mount_point" "mount_point")
|
|
|
|
local image_dir="$COMPOSEFS_DIR/images/$image_name"
|
|
|
|
log_info "Mounting composefs image: $image_name at $mount_point" "composefs-alternative"
|
|
|
|
if [[ ! -d "$image_dir" ]]; then
|
|
log_error "Composefs image does not exist: $image_name" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -d "$mount_point" ]]; then
|
|
if ! mkdir -p "$mount_point"; then
|
|
log_error "Failed to create mount point: $mount_point" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Check if mount point is already in use
|
|
if mountpoint -q "$mount_point" 2>/dev/null; then
|
|
log_error "Mount point is already in use: $mount_point (unmount existing mount first)" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if mount point is writable (for overlay upper directory)
|
|
if [[ ! -w "$(dirname "$mount_point")" ]]; then
|
|
log_error "Cannot create mount point: parent directory is not writable: $(dirname "$mount_point")" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
|
|
# Read metadata
|
|
local metadata_file="$image_dir/metadata.json"
|
|
if [[ ! -f "$metadata_file" ]]; then
|
|
log_error "Metadata file not found: $metadata_file" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
|
|
# Get layer IDs
|
|
local layers
|
|
layers=$(jq -r '.layers[]' "$metadata_file")
|
|
|
|
# Mount all layers with progress indication
|
|
local lower_dirs=""
|
|
local first_layer=true
|
|
local layer_count=0
|
|
local total_layers
|
|
total_layers=$(jq -r '.layers | length' "$metadata_file")
|
|
|
|
while IFS= read -r layer_id; do
|
|
layer_count=$((layer_count + 1))
|
|
log_info "Mounting layer $layer_count/$total_layers: $layer_id" "composefs-alternative"
|
|
|
|
# Mount layer using squashfs
|
|
mount_layer "$layer_id"
|
|
local layer_mount="$COMPOSEFS_DIR/layers/$layer_id/mount"
|
|
|
|
if [[ "$first_layer" == true ]]; then
|
|
lower_dirs="$layer_mount"
|
|
first_layer=false
|
|
else
|
|
lower_dirs="${lower_dirs}:${layer_mount}"
|
|
fi
|
|
done <<< "$layers"
|
|
|
|
# Create overlay mount
|
|
local upper_dir="$COMPOSEFS_DIR/mounts/$(basename "$mount_point")_upper"
|
|
local work_dir="$COMPOSEFS_DIR/mounts/$(basename "$mount_point")_work"
|
|
|
|
mkdir -p "$upper_dir" "$work_dir"
|
|
|
|
log_info "Creating overlay mount..." "composefs-alternative"
|
|
if mount -t overlay overlay -o "lowerdir=$lower_dirs,upperdir=$upper_dir,workdir=$work_dir" "$mount_point"; then
|
|
log_info "Composefs image mounted at: $mount_point" "composefs-alternative"
|
|
log_info "Upper directory: $upper_dir" "composefs-alternative"
|
|
log_info "Work directory: $work_dir" "composefs-alternative"
|
|
|
|
# Record mount information
|
|
local mount_info="$COMPOSEFS_DIR/mounts/$(basename "$mount_point").json"
|
|
cat > "$mount_info" <<EOF
|
|
{
|
|
"image": "$image_name",
|
|
"mount_point": "$mount_point",
|
|
"upper_dir": "$upper_dir",
|
|
"work_dir": "$work_dir",
|
|
"lower_dirs": "$lower_dirs",
|
|
"mounted_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
}
|
|
EOF
|
|
else
|
|
log_error "Failed to mount composefs image (check if overlay module is loaded)" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Unmount a composefs image with proper cleanup
|
|
unmount_image() {
|
|
local mount_point="$1"
|
|
|
|
# SECURITY: Validate mount point
|
|
mount_point=$(validate_path "$mount_point" "mount_point")
|
|
|
|
local mount_info="$COMPOSEFS_DIR/mounts/$(basename "$mount_point").json"
|
|
|
|
log_info "Unmounting composefs image at: $mount_point" "composefs-alternative"
|
|
|
|
if [[ ! -f "$mount_info" ]]; then
|
|
log_error "Mount information not found for: $mount_point" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
|
|
# Read mount information
|
|
local upper_dir
|
|
local work_dir
|
|
local lower_dirs
|
|
upper_dir=$(jq -r '.upper_dir' "$mount_info")
|
|
work_dir=$(jq -r '.work_dir' "$mount_info")
|
|
lower_dirs=$(jq -r '.lower_dirs' "$mount_info")
|
|
|
|
# Unmount overlay with lazy fallback
|
|
if umount "$mount_point"; then
|
|
log_info "Composefs image unmounted from: $mount_point" "composefs-alternative"
|
|
|
|
# Clean up overlay directories
|
|
rm -rf "$upper_dir" "$work_dir"
|
|
rm -f "$mount_info"
|
|
|
|
# Unmount and clean up layer mounts
|
|
IFS=':' read -ra LAYER_MOUNTS <<< "$lower_dirs"
|
|
for layer_mount in "${LAYER_MOUNTS[@]}"; do
|
|
if mountpoint -q "$layer_mount" 2>/dev/null; then
|
|
umount "$layer_mount" 2>/dev/null || log_warning "Failed to unmount layer: $layer_mount" "composefs-alternative"
|
|
fi
|
|
done
|
|
|
|
# Remove mount point if empty
|
|
rmdir "$mount_point" 2>/dev/null || true
|
|
else
|
|
log_warning "Standard unmount failed, attempting lazy unmount" "composefs-alternative"
|
|
if umount -l "$mount_point"; then
|
|
log_info "Composefs image lazy unmounted from: $mount_point" "composefs-alternative"
|
|
|
|
# Clean up overlay directories
|
|
rm -rf "$upper_dir" "$work_dir"
|
|
rm -f "$mount_info"
|
|
|
|
# Unmount and clean up layer mounts
|
|
IFS=':' read -ra LAYER_MOUNTS <<< "$lower_dirs"
|
|
for layer_mount in "${LAYER_MOUNTS[@]}"; do
|
|
if mountpoint -q "$layer_mount" 2>/dev/null; then
|
|
umount "$layer_mount" 2>/dev/null || log_warning "Failed to unmount layer: $layer_mount" "composefs-alternative"
|
|
fi
|
|
done
|
|
|
|
# Remove mount point if empty
|
|
rmdir "$mount_point" 2>/dev/null || true
|
|
else
|
|
log_error "Failed to unmount composefs image from: $mount_point (even with lazy unmount)" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# --- END OF SCRIPTLET: 04-images.sh ---
|
|
|
|
# ============================================================================
|
|
# Listing and Reporting Functions
|
|
# ============================================================================
|
|
# Listing and reporting functions for Ubuntu uBlue ComposeFS Alternative
|
|
|
|
# List composefs images
|
|
list_images() {
|
|
log_info "Currently available Composefs Images:" "composefs-alternative"
|
|
|
|
if [[ ! -d "$COMPOSEFS_DIR/images" ]]; then
|
|
log_info "No images found" "composefs-alternative"
|
|
return 0
|
|
fi
|
|
|
|
for image_dir in "$COMPOSEFS_DIR/images"/*; do
|
|
if [[ -d "$image_dir" ]]; then
|
|
local image_name
|
|
image_name=$(basename "$image_dir")
|
|
local metadata_file="$image_dir/metadata.json"
|
|
|
|
if [[ -f "$metadata_file" ]]; then
|
|
local created
|
|
local layers
|
|
local layer_ids=()
|
|
created=$(jq -r '.created' "$metadata_file")
|
|
layers=$(jq -r '.layers | length' "$metadata_file")
|
|
|
|
# Collect layer IDs for size calculation
|
|
while IFS= read -r layer_id; do
|
|
layer_ids+=("$layer_id")
|
|
done < <(jq -r '.layers[]' "$metadata_file")
|
|
|
|
# Calculate actual disk usage (accounting for deduplication)
|
|
local actual_size
|
|
actual_size=$(calculate_layer_disk_usage "${layer_ids[@]}")
|
|
local actual_size_mb=$((actual_size / 1024 / 1024))
|
|
|
|
# Calculate total size (including duplicates for reference)
|
|
local total_size=0
|
|
for layer_id in "${layer_ids[@]}"; do
|
|
local layer_dir="$COMPOSEFS_DIR/layers/$layer_id"
|
|
local layer_metadata="$layer_dir/metadata.json"
|
|
if [[ -f "$layer_metadata" ]]; then
|
|
local layer_size
|
|
layer_size=$(jq -r '.size' "$layer_metadata")
|
|
total_size=$((total_size + layer_size))
|
|
fi
|
|
done
|
|
local total_size_mb=$((total_size / 1024 / 1024))
|
|
|
|
# Show both actual and total sizes
|
|
if [[ $actual_size -eq $total_size ]]; then
|
|
echo " $image_name (created: $created, layers: $layers, size: ${actual_size_mb}MB)"
|
|
else
|
|
local savings_mb=$((total_size_mb - actual_size_mb))
|
|
echo " $image_name (created: $created, layers: $layers, size: ${actual_size_mb}MB, total: ${total_size_mb}MB, saved: ${savings_mb}MB via deduplication)"
|
|
fi
|
|
else
|
|
echo " $image_name (incomplete)"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
# List mounted composefs images
|
|
list_mounts() {
|
|
log_info "Currently Mounted Composefs Images:" "composefs-alternative"
|
|
|
|
if [[ ! -d "$COMPOSEFS_DIR/mounts" ]]; then
|
|
log_info "No mounts found" "composefs-alternative"
|
|
return 0
|
|
fi
|
|
|
|
local mount_count=0
|
|
for mount_info in "$COMPOSEFS_DIR/mounts"/*.json; do
|
|
if [[ -f "$mount_info" ]]; then
|
|
local mount_point
|
|
local image_name
|
|
local mounted_at
|
|
mount_point=$(jq -r '.mount_point' "$mount_info")
|
|
image_name=$(jq -r '.image' "$mount_info")
|
|
mounted_at=$(jq -r '.mounted_at' "$mount_info")
|
|
|
|
echo " $image_name -> $mount_point (mounted: $mounted_at)"
|
|
mount_count=$((mount_count + 1))
|
|
fi
|
|
done
|
|
|
|
if [[ $mount_count -eq 0 ]]; then
|
|
log_info "No active mounts" "composefs-alternative"
|
|
fi
|
|
}
|
|
|
|
# PERFORMANCE: Optimized layer listing with caching
|
|
list_layers() {
|
|
log_info "Available Composefs Layers:" "composefs-alternative"
|
|
|
|
if [[ ! -d "$COMPOSEFS_DIR/layers" ]]; then
|
|
log_info "No layers found" "composefs-alternative"
|
|
return 0
|
|
fi
|
|
|
|
local layer_count=0
|
|
local total_size=0
|
|
|
|
# Pre-calculate layer references for better performance
|
|
local -A layer_references
|
|
for image_dir in "$COMPOSEFS_DIR/images"/*; do
|
|
if [[ -d "$image_dir" ]]; then
|
|
local image_metadata="$image_dir/metadata.json"
|
|
if [[ -f "$image_metadata" ]]; then
|
|
while IFS= read -r layer_id; do
|
|
layer_references["$layer_id"]=$((layer_references["$layer_id"] + 1))
|
|
done < <(jq -r '.layers[]' "$image_metadata")
|
|
fi
|
|
fi
|
|
done
|
|
|
|
for layer_dir in "$COMPOSEFS_DIR/layers"/*; do
|
|
if [[ -d "$layer_dir" ]]; then
|
|
local layer_id
|
|
layer_id=$(basename "$layer_dir")
|
|
local metadata_file="$layer_dir/metadata.json"
|
|
|
|
if [[ -f "$metadata_file" ]]; then
|
|
local created
|
|
local size
|
|
local source
|
|
local reference_count=${layer_references["$layer_id"]:-0}
|
|
created=$(jq -r '.created' "$metadata_file")
|
|
size=$(jq -r '.size' "$metadata_file")
|
|
source=$(jq -r '.source' "$metadata_file")
|
|
|
|
local size_mb=$((size / 1024 / 1024))
|
|
total_size=$((total_size + size))
|
|
|
|
if [[ $reference_count -eq 0 ]]; then
|
|
echo " $layer_id (created: $created, size: ${size_mb}MB, source: $source, references: 0 - UNREFERENCED)"
|
|
elif [[ $reference_count -eq 1 ]]; then
|
|
echo " $layer_id (created: $created, size: ${size_mb}MB, source: $source, references: $reference_count)"
|
|
else
|
|
echo " $layer_id (created: $created, size: ${size_mb}MB, source: $source, references: $reference_count - SHARED)"
|
|
fi
|
|
layer_count=$((layer_count + 1))
|
|
else
|
|
echo " $layer_id (incomplete)"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
local total_size_mb=$((total_size / 1024 / 1024))
|
|
echo
|
|
log_info "Layer Summary: $layer_count layers, total size: ${total_size_mb}MB" "composefs-alternative"
|
|
}
|
|
|
|
# Show comprehensive system status
|
|
show_status() {
|
|
echo "=== Composefs Alternative Status ==="
|
|
echo
|
|
|
|
# System information
|
|
echo "System Information:"
|
|
get_system_info
|
|
echo
|
|
|
|
# Storage information
|
|
echo "Storage Information:"
|
|
local composefs_size
|
|
composefs_size=$(du -sb "$COMPOSEFS_DIR" 2>/dev/null | cut -f1 || echo "0")
|
|
local composefs_size_mb=$((composefs_size / 1024 / 1024))
|
|
echo " Composefs directory size: ${composefs_size_mb}MB"
|
|
|
|
local available_space_mb
|
|
available_space_mb=$(get_available_space "$COMPOSEFS_DIR")
|
|
local available_space_gb=$((available_space_mb / 1024))
|
|
echo " Available space: ${available_space_gb}GB"
|
|
echo
|
|
|
|
# Images and layers
|
|
list_images
|
|
echo
|
|
list_mounts
|
|
echo
|
|
list_layers
|
|
echo
|
|
|
|
# Health check
|
|
echo "Health Check:"
|
|
local health_issues=0
|
|
|
|
# Check for orphaned mounts
|
|
for mount_info in "$COMPOSEFS_DIR/mounts"/*.json; do
|
|
if [[ -f "$mount_info" ]]; then
|
|
local mount_point
|
|
mount_point=$(jq -r '.mount_point' "$mount_info" 2>/dev/null)
|
|
if [[ -n "$mount_point" && "$mount_point" != "null" ]]; then
|
|
if ! mountpoint -q "$mount_point" 2>/dev/null; then
|
|
echo " ⚠️ Orphaned mount info: $mount_info"
|
|
health_issues=$((health_issues + 1))
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Check for unreferenced layers
|
|
local unreferenced_count=0
|
|
for layer_dir in "$COMPOSEFS_DIR/layers"/*; do
|
|
if [[ -d "$layer_dir" ]]; then
|
|
local layer_id
|
|
layer_id=$(basename "$layer_dir")
|
|
local is_referenced=false
|
|
|
|
for image_dir in "$COMPOSEFS_DIR/images"/*; do
|
|
if [[ -d "$image_dir" ]]; then
|
|
local metadata_file="$image_dir/metadata.json"
|
|
if [[ -f "$metadata_file" ]]; then
|
|
if jq -e --arg layer "$layer_id" '.layers[] | select(. == $layer)' "$metadata_file" >/dev/null 2>&1; then
|
|
is_referenced=true
|
|
break
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [[ "$is_referenced" == false ]]; then
|
|
unreferenced_count=$((unreferenced_count + 1))
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [[ $unreferenced_count -gt 0 ]]; then
|
|
echo " ⚠️ $unreferenced_count unreferenced layers found (run 'cleanup' to remove)"
|
|
health_issues=$((health_issues + 1))
|
|
fi
|
|
|
|
if [[ $health_issues -eq 0 ]]; then
|
|
echo " ✓ System healthy"
|
|
else
|
|
echo " ⚠️ $health_issues health issues found"
|
|
fi
|
|
}
|
|
|
|
# --- END OF SCRIPTLET: 05-listing.sh ---
|
|
|
|
# ============================================================================
|
|
# Cleanup and Maintenance Functions
|
|
# ============================================================================
|
|
# Cleanup and maintenance functions for Ubuntu uBlue ComposeFS Alternative
|
|
|
|
# Remove unreferenced layers
|
|
cleanup_unreferenced_layers() {
|
|
log_info "Cleaning up unreferenced layers" "composefs-alternative"
|
|
|
|
if [[ ! -d "$COMPOSEFS_DIR/layers" ]]; then
|
|
log_info "No layers to clean up" "composefs-alternative"
|
|
return 0
|
|
fi
|
|
|
|
# Get all referenced layer IDs from images
|
|
local referenced_layers=()
|
|
for image_dir in "$COMPOSEFS_DIR/images"/*; do
|
|
if [[ -d "$image_dir" ]]; then
|
|
local metadata_file="$image_dir/metadata.json"
|
|
if [[ -f "$metadata_file" ]]; then
|
|
while IFS= read -r layer_id; do
|
|
referenced_layers+=("$layer_id")
|
|
done < <(jq -r '.layers[]' "$metadata_file")
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Check each layer
|
|
local removed_count=0
|
|
for layer_dir in "$COMPOSEFS_DIR/layers"/*; do
|
|
if [[ -d "$layer_dir" ]]; then
|
|
local layer_id
|
|
layer_id=$(basename "$layer_dir")
|
|
|
|
# Check if layer is referenced
|
|
local is_referenced=false
|
|
for ref_layer in "${referenced_layers[@]}"; do
|
|
if [[ "$ref_layer" == "$layer_id" ]]; then
|
|
is_referenced=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ "$is_referenced" == false ]]; then
|
|
log_info "Removing unreferenced layer: $layer_id" "composefs-alternative"
|
|
rm -rf "$layer_dir"
|
|
removed_count=$((removed_count + 1))
|
|
fi
|
|
fi
|
|
done
|
|
|
|
log_info "Cleaned up $removed_count unreferenced layers" "composefs-alternative"
|
|
}
|
|
|
|
# Remove a composefs image
|
|
remove_image() {
|
|
local image_name="$1"
|
|
|
|
# SECURITY: Validate image name
|
|
image_name=$(validate_image_name "$image_name")
|
|
|
|
local image_dir="$COMPOSEFS_DIR/images/$image_name"
|
|
|
|
log_info "Removing composefs image: $image_name" "composefs-alternative"
|
|
|
|
if [[ ! -d "$image_dir" ]]; then
|
|
log_error "Composefs image does not exist: $image_name" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if image is mounted
|
|
for mount_info in "$COMPOSEFS_DIR/mounts"/*.json; do
|
|
if [[ -f "$mount_info" ]]; then
|
|
local mounted_image
|
|
mounted_image=$(jq -r '.image' "$mount_info")
|
|
if [[ "$mounted_image" == "$image_name" ]]; then
|
|
local mount_point
|
|
mount_point=$(jq -r '.mount_point' "$mount_info")
|
|
log_error "Cannot remove image while mounted at: $mount_point. Unmount first." "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Remove image directory
|
|
rm -rf "$image_dir"
|
|
log_info "Composefs image removed: $image_name" "composefs-alternative"
|
|
|
|
# Clean up unreferenced layers
|
|
cleanup_unreferenced_layers
|
|
}
|
|
|
|
# Clean up orphaned mount information
|
|
cleanup_orphaned_mounts() {
|
|
log_info "Cleaning up orphaned mount information" "composefs-alternative"
|
|
|
|
if [[ ! -d "$COMPOSEFS_DIR/mounts" ]]; then
|
|
log_info "No mount information to clean up" "composefs-alternative"
|
|
return 0
|
|
fi
|
|
|
|
local cleaned_count=0
|
|
for mount_info in "$COMPOSEFS_DIR/mounts"/*.json; do
|
|
if [[ -f "$mount_info" ]]; then
|
|
local mount_point
|
|
mount_point=$(jq -r '.mount_point' "$mount_info" 2>/dev/null)
|
|
if [[ -n "$mount_point" && "$mount_point" != "null" ]]; then
|
|
if ! mountpoint -q "$mount_point" 2>/dev/null; then
|
|
log_info "Removing orphaned mount info: $mount_info" "composefs-alternative"
|
|
rm -f "$mount_info"
|
|
cleaned_count=$((cleaned_count + 1))
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
log_info "Cleaned up $cleaned_count orphaned mount information files" "composefs-alternative"
|
|
}
|
|
|
|
# Full system cleanup
|
|
full_cleanup() {
|
|
log_info "Performing full system cleanup" "composefs-alternative"
|
|
|
|
# Clean up unreferenced layers
|
|
cleanup_unreferenced_layers
|
|
|
|
# Clean up orphaned mount information
|
|
cleanup_orphaned_mounts
|
|
|
|
# Clean up empty directories
|
|
find "$COMPOSEFS_DIR" -type d -empty -delete 2>/dev/null || true
|
|
|
|
log_info "Full system cleanup completed" "composefs-alternative"
|
|
}
|
|
|
|
# --- END OF SCRIPTLET: 06-cleanup.sh ---
|
|
|
|
# ============================================================================
|
|
# Main Dispatch and Help
|
|
# ============================================================================
|
|
# Main dispatch and help system for Ubuntu uBlue ComposeFS Alternative
|
|
|
|
# Show usage information
|
|
show_usage() {
|
|
cat << EOF
|
|
composefs-alternative.sh - Ubuntu alternative to composefs
|
|
Provides similar functionality to composefs using overlayfs and other Linux filesystem features
|
|
|
|
Usage: $0 <command> [options]
|
|
|
|
Commands:
|
|
create <image_name> <source_dir1> [source_dir2] ... Create composefs image from directories
|
|
mount <image_name> <mount_point> Mount composefs image
|
|
unmount <mount_point> Unmount composefs image
|
|
list-images List all composefs images
|
|
list-mounts List mounted composefs images
|
|
list-layers List all layers with usage info
|
|
remove <image_name> Remove composefs image
|
|
cleanup Clean up unreferenced layers
|
|
status Show comprehensive system status
|
|
|
|
Examples:
|
|
$0 create my-image /path/to/base /path/to/apps
|
|
$0 mount my-image /mnt/composefs
|
|
$0 list-images
|
|
$0 list-layers
|
|
$0 unmount /mnt/composefs
|
|
$0 remove my-image
|
|
$0 cleanup
|
|
$0 status
|
|
|
|
Features:
|
|
- Content-addressable layers with deduplication
|
|
- Immutable layers using squashfs
|
|
- Multi-layer image support
|
|
- Automatic cleanup of unreferenced layers
|
|
- Proper resource management and cleanup
|
|
- Accurate disk usage reporting (accounting for deduplication)
|
|
- Enhanced security with input validation
|
|
- Progress indicators for long operations
|
|
- Optimized performance for large datasets
|
|
- True parallel processing for hash generation
|
|
- Comprehensive system health monitoring
|
|
- Robust error handling with fallback mechanisms
|
|
|
|
Security Features:
|
|
- Path traversal protection
|
|
- Input validation and sanitization
|
|
- Secure temporary file handling
|
|
- Privilege escalation prevention
|
|
- Character set restrictions on inputs
|
|
- Absolute path enforcement for critical operations
|
|
|
|
Performance Features:
|
|
- Parallel hash generation using xargs
|
|
- Cached layer reference counting
|
|
- Optimized SquashFS operations with compression info
|
|
- Progress indication for all long operations
|
|
- Fallback to sequential processing if parallel fails
|
|
- Memory-efficient processing of large datasets
|
|
|
|
System Requirements:
|
|
- Linux kernel with squashfs and overlay modules
|
|
- squashfs-tools package
|
|
- jq for JSON processing
|
|
- coreutils for system utilities
|
|
- Root privileges for filesystem operations
|
|
|
|
This script provides composefs-like functionality using overlayfs for Ubuntu systems.
|
|
Based on composefs design principles: https://github.com/containers/composefs
|
|
|
|
EOF
|
|
}
|
|
|
|
# Main function
|
|
main() {
|
|
# Check if running as root
|
|
check_root
|
|
|
|
# Check dependencies
|
|
check_dependencies
|
|
|
|
# Check kernel modules
|
|
check_kernel_modules
|
|
|
|
# Initialize directories
|
|
init_directories
|
|
|
|
# Parse command line arguments
|
|
if [[ $# -eq 0 ]]; then
|
|
show_usage
|
|
exit 1
|
|
fi
|
|
|
|
local command="$1"
|
|
shift
|
|
|
|
case "$command" in
|
|
"create")
|
|
if [[ $# -lt 2 ]]; then
|
|
log_error "Usage: create <image_name> <source_dir1> [source_dir2] ..." "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
local image_name="$1"
|
|
shift
|
|
create_image "$image_name" "$@"
|
|
;;
|
|
"mount")
|
|
if [[ $# -lt 2 ]]; then
|
|
log_error "Usage: mount <image_name> <mount_point>" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
mount_image "$1" "$2"
|
|
;;
|
|
"unmount")
|
|
if [[ $# -lt 1 ]]; then
|
|
log_error "Usage: unmount <mount_point>" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
unmount_image "$1"
|
|
;;
|
|
"list-images")
|
|
list_images
|
|
;;
|
|
"list-mounts")
|
|
list_mounts
|
|
;;
|
|
"list-layers")
|
|
list_layers
|
|
;;
|
|
"remove")
|
|
if [[ $# -lt 1 ]]; then
|
|
log_error "Usage: remove <image_name>" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
remove_image "$1"
|
|
;;
|
|
"cleanup")
|
|
cleanup_unreferenced_layers
|
|
;;
|
|
"status")
|
|
show_status
|
|
;;
|
|
"help"|"-h"|"--help")
|
|
show_usage
|
|
;;
|
|
*)
|
|
log_error "Unknown command: $command" "composefs-alternative"
|
|
show_usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Run main function with all arguments
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
main "$@"
|
|
fi
|
|
|
|
# --- END OF SCRIPTLET: 99-main.sh ---
|
|
|
|
# ============================================================================
|
|
# Embedded Configuration Files
|
|
# ============================================================================
|
|
|
|
# ============================================================================
|
|
# 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/composefs-alternative/config/${config_name}.json"
|
|
if [[ -f "$config_file" ]]; then
|
|
jq -r '.' "$config_file"
|
|
else
|
|
log_error "Configuration file not found: $config_file" "composefs-alternative"
|
|
exit 1
|
|
fi
|
|
}
|
|
|