particle-os-tools/src/apt-layer/scriptlets/05-live-overlay.sh
2025-07-15 11:18:41 -07:00

556 lines
18 KiB
Bash
Raw Blame History

#!/bin/bash
# Ubuntu uBlue apt-layer Live Overlay System
# Implements live system layering similar to rpm-ostree
# Uses overlayfs for live package installation and management
# =============================================================================
# LIVE OVERLAY SYSTEM FUNCTIONS
# =============================================================================
# Live overlay state file (with fallbacks for when load_path_config() is not available)
# These will be overridden by load_path_config() when available
LIVE_OVERLAY_STATE_FILE="${LIVE_OVERLAY_STATE_FILE:-/var/lib/apt-layer/live-overlay.state}"
LIVE_OVERLAY_MOUNT_POINT="${LIVE_OVERLAY_MOUNT_POINT:-/var/lib/apt-layer/live-overlay/mount}"
LIVE_OVERLAY_PACKAGE_LOG="${LIVE_OVERLAY_PACKAGE_LOG:-/var/log/apt-layer/live-overlay-packages.log}"
LIVE_OVERLAY_DIR="${LIVE_OVERLAY_DIR:-/var/lib/apt-layer/live-overlay}"
LIVE_OVERLAY_UPPER_DIR="${LIVE_OVERLAY_UPPER_DIR:-/var/lib/apt-layer/live-overlay/upper}"
LIVE_OVERLAY_WORK_DIR="${LIVE_OVERLAY_WORK_DIR:-/var/lib/apt-layer/live-overlay/work}"
# Initialize live overlay system
init_live_overlay_system() {
log_info "Initializing live overlay system" "apt-layer"
# Load system paths if available
if command -v load_path_config >/dev/null 2>&1; then
load_path_config
fi
# Create live overlay directories
mkdir -p "$LIVE_OVERLAY_DIR" "$LIVE_OVERLAY_UPPER_DIR" "$LIVE_OVERLAY_WORK_DIR"
mkdir -p "$LIVE_OVERLAY_MOUNT_POINT"
# Set proper permissions (use sudo if needed)
if [[ $EUID -eq 0 ]]; then
# Running as root, use chmod directly
chmod 755 "$LIVE_OVERLAY_DIR" 2>/dev/null || true
chmod 700 "$LIVE_OVERLAY_UPPER_DIR" "$LIVE_OVERLAY_WORK_DIR" 2>/dev/null || true
else
# Running as regular user, use sudo
sudo chmod 755 "$LIVE_OVERLAY_DIR" 2>/dev/null || true
sudo chmod 700 "$LIVE_OVERLAY_UPPER_DIR" "$LIVE_OVERLAY_WORK_DIR" 2>/dev/null || true
fi
# Initialize package log if it doesn't exist
if [[ ! -f "$LIVE_OVERLAY_PACKAGE_LOG" ]]; then
touch "$LIVE_OVERLAY_PACKAGE_LOG"
if [[ $EUID -eq 0 ]]; then
chmod 644 "$LIVE_OVERLAY_PACKAGE_LOG" 2>/dev/null || true
else
sudo chmod 644 "$LIVE_OVERLAY_PACKAGE_LOG" 2>/dev/null || true
fi
fi
# Conditional DNS fix for chroot overlay (WSL, etc)
if [[ -d "$LIVE_OVERLAY_MOUNT_POINT" ]]; then
if ! chroot "$LIVE_OVERLAY_MOUNT_POINT" getent hosts archive.ubuntu.com >/dev/null 2>&1; then
log_warning "DNS resolution failed in overlay. Injecting public DNS servers..." "apt-layer"
# Backup original resolv.conf if present
if [[ -f "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf" ]]; then
cp "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf" "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf.aptlayerbak"
fi
echo "nameserver 8.8.8.8" > "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf"
echo "nameserver 1.1.1.1" >> "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf"
chmod 644 "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf"
touch "$LIVE_OVERLAY_MOUNT_POINT/.dns_fixed_by_apt_layer"
log_success "DNS configuration applied to overlay" "apt-layer"
else
log_info "DNS resolution in overlay is working. No changes made." "apt-layer"
fi
fi
log_success "Live overlay system initialized" "apt-layer"
}
# Check if live overlay is active
is_live_overlay_active() {
if [[ -f "$LIVE_OVERLAY_STATE_FILE" ]]; then
local state
state=$(cat "$LIVE_OVERLAY_STATE_FILE" 2>/dev/null || echo "")
[[ "$state" == "active" ]]
else
false
fi
}
# Check if system supports live overlay
check_live_overlay_support() {
local errors=0
local test_dir="/tmp/overlay-test-$$"
local test_lower="$test_dir/lower"
local test_upper="$test_dir/upper"
local test_work="$test_dir/work"
local test_mount="$test_dir/mount"
# Check for overlay module
if ! modprobe -n overlay >/dev/null 2>&1; then
log_error "Overlay module not available" "apt-layer"
errors=$((errors + 1))
fi
# Create test directories
mkdir -p "$test_lower" "$test_upper" "$test_work" "$test_mount" 2>/dev/null
# Check for overlayfs mount support
if ! mount -t overlay overlay -o "lowerdir=$test_lower,upperdir=$test_upper,workdir=$test_work" "$test_mount" 2>/dev/null; then
log_error "Overlayfs mount not supported" "apt-layer"
errors=$((errors + 1))
else
umount "$test_mount" 2>/dev/null
fi
# Cleanup test directories
rm -rf "$test_dir" 2>/dev/null
# Check for read-only root filesystem
if ! is_root_readonly; then
log_warning "Root filesystem is not read-only - live overlay may not be necessary" "apt-layer"
fi
if [[ $errors -gt 0 ]]; then
return 1
fi
return 0
}
# Check if root filesystem is read-only
is_root_readonly() {
local root_mount
root_mount=$(findmnt -n -o OPTIONS / | grep -o "ro" || echo "")
[[ -n "$root_mount" ]]
}
# Start live overlay
start_live_overlay() {
log_info "Starting live overlay system" "apt-layer"
# Check if already active
if is_live_overlay_active; then
log_warning "Live overlay is already active" "apt-layer"
return 0
fi
# Check system support
if ! check_live_overlay_support; then
log_error "System does not support live overlay" "apt-layer"
return 1
fi
# Initialize system
init_live_overlay_system
# Create overlay mount
log_info "Creating overlay mount" "apt-layer"
if mount -t overlay overlay -o "lowerdir=/,upperdir=$LIVE_OVERLAY_UPPER_DIR,workdir=$LIVE_OVERLAY_WORK_DIR" "$LIVE_OVERLAY_MOUNT_POINT"; then
log_success "Overlay mount created successfully" "apt-layer"
# Mark overlay as active
echo "active" > "$LIVE_OVERLAY_STATE_FILE"
log_success "Live overlay started successfully" "apt-layer"
log_info "Changes will be applied to overlay and can be committed or rolled back" "apt-layer"
return 0
else
log_error "Failed to create overlay mount" "apt-layer"
return 1
fi
}
# Stop live overlay
stop_live_overlay() {
log_info "Stopping live overlay system" "apt-layer"
# Check if overlay is active
if ! is_live_overlay_active; then
log_warning "Live overlay is not active" "apt-layer"
return 0
fi
# Check for active processes
if check_active_processes; then
log_warning "Active processes detected - overlay will persist until processes complete" "apt-layer"
return 0
fi
# Undo DNS fix if we applied it
if [[ -f "$LIVE_OVERLAY_MOUNT_POINT/.dns_fixed_by_apt_layer" ]]; then
if [[ -f "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf.aptlayerbak" ]]; then
mv "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf.aptlayerbak" "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf"
else
rm -f "$LIVE_OVERLAY_MOUNT_POINT/etc/resolv.conf"
fi
rm -f "$LIVE_OVERLAY_MOUNT_POINT/.dns_fixed_by_apt_layer"
log_info "DNS fix by apt-layer undone on overlay stop" "apt-layer"
fi
# Unmount overlay
log_info "Unmounting overlay" "apt-layer"
if umount "$LIVE_OVERLAY_MOUNT_POINT"; then
log_success "Overlay unmounted successfully" "apt-layer"
# Remove state file
rm -f "$LIVE_OVERLAY_STATE_FILE"
log_success "Live overlay stopped successfully" "apt-layer"
return 0
else
log_error "Failed to unmount overlay" "apt-layer"
return 1
fi
}
# Check for active processes that might prevent unmounting
check_active_processes() {
# Check for package manager processes
if pgrep -f "apt|dpkg|apt-get" >/dev/null 2>&1; then
return 0
fi
# Check for processes using the overlay mount
if lsof "$LIVE_OVERLAY_MOUNT_POINT" >/dev/null 2>&1; then
return 0
fi
return 1
}
# Get live overlay status
get_live_overlay_status() {
echo "=== Live Overlay Status ==="
if is_live_overlay_active; then
log_success "<22> Live overlay is ACTIVE" "apt-layer"
# Show mount details
if mountpoint -q "$LIVE_OVERLAY_MOUNT_POINT"; then
log_info "Overlay mount point: $LIVE_OVERLAY_MOUNT_POINT" "apt-layer"
# Show overlay usage
if [[ -d "$LIVE_OVERLAY_UPPER_DIR" ]]; then
local usage=$(du -sh "$LIVE_OVERLAY_UPPER_DIR" 2>/dev/null | cut -f1 || echo "unknown")
log_info "Overlay usage: $usage" "apt-layer"
fi
# Show installed packages
if [[ -f "$LIVE_OVERLAY_PACKAGE_LOG" ]]; then
local package_count=$(wc -l < "$LIVE_OVERLAY_PACKAGE_LOG" 2>/dev/null || echo "0")
log_info "Packages installed in overlay: $package_count" "apt-layer"
fi
else
log_warning "<22><> Overlay mount point not mounted" "apt-layer"
fi
# Check for active processes
if check_active_processes; then
log_warning "<22><> Active processes detected - overlay cannot be stopped" "apt-layer"
fi
else
log_info "<22> Live overlay is not active" "apt-layer"
# Check if system supports live overlay
if check_live_overlay_support >/dev/null 2>&1; then
log_info "<22> System supports live overlay" "apt-layer"
log_info "Use '--live-overlay start' to start live overlay" "apt-layer"
else
log_warning "<22><> System does not support live overlay" "apt-layer"
fi
fi
echo ""
}
# Install packages in live overlay
live_install() {
local packages=("$@")
log_info "Installing packages in live overlay: ${packages[*]}" "apt-layer"
# Check if overlay is active
if ! is_live_overlay_active; then
log_error "Live overlay is not active" "apt-layer"
log_info "Use '--live-overlay start' to start live overlay first" "apt-layer"
return 1
fi
# Check for root privileges
if [[ $EUID -ne 0 ]]; then
log_error "Root privileges required for live installation" "apt-layer"
return 1
fi
# Update package lists in overlay
log_info "Updating package lists in overlay" "apt-layer"
if ! chroot "$LIVE_OVERLAY_MOUNT_POINT" apt-get update; then
log_error "Failed to update package lists" "apt-layer"
log_warning "Network or DNS error? For offline or WSL overlays, use: apt-layer --live-dpkg <.deb files>" "apt-layer"
return 1
fi
# Install packages in overlay
log_info "Installing packages in overlay" "apt-layer"
if chroot "$LIVE_OVERLAY_MOUNT_POINT" apt-get install -y "${packages[@]}"; then
log_success "Packages installed successfully in overlay" "apt-layer"
# Log installed packages
for package in "${packages[@]}"; do
echo "$(date '+%Y-%m-%d %H:%M:%S') - INSTALLED: $package" >> "$LIVE_OVERLAY_PACKAGE_LOG"
done
log_info "Changes are applied to overlay and can be committed or rolled back" "apt-layer"
return 0
else
log_error "Failed to install packages in overlay" "apt-layer"
log_warning "If this is a network or DNS issue, try: apt-layer --live-dpkg <.deb files>" "apt-layer"
return 1
fi
}
# Manage live overlay
manage_live_overlay() {
local action="$1"
shift
local options=("$@")
case "$action" in
"start")
start_live_overlay
;;
"stop")
stop_live_overlay
;;
"status")
get_live_overlay_status
;;
"commit")
local message="${options[0]:-Live overlay changes}"
commit_live_overlay "$message"
;;
"rollback")
rollback_live_overlay
;;
"list")
list_live_overlay_packages
;;
"clean")
clean_live_overlay
;;
*)
log_error "Unknown live overlay action: $action" "apt-layer"
log_info "Valid actions: start, stop, status, commit, rollback, list, clean" "apt-layer"
return 1
;;
esac
}
# Commit live overlay changes
commit_live_overlay() {
local message="$1"
log_info "Committing live overlay changes: $message" "apt-layer"
# Check if overlay is active
if ! is_live_overlay_active; then
log_error "Live overlay is not active" "apt-layer"
return 1
fi
# Check if there are changes to commit
if ! has_overlay_changes; then
log_warning "No changes to commit" "apt-layer"
return 0
fi
# Create new ComposeFS layer from overlay changes
local timestamp=$(date '+%Y%m%d_%H%M%S')
local layer_name="live-overlay-commit-${timestamp}"
log_info "Creating new layer: $layer_name" "apt-layer"
# Create layer from overlay changes
if create_layer_from_overlay "$layer_name" "$message"; then
log_success "Live overlay changes committed as layer: $layer_name" "apt-layer"
# Clean up overlay
clean_live_overlay
return 0
else
log_error "Failed to commit live overlay changes" "apt-layer"
return 1
fi
}
# Check if overlay has changes
has_overlay_changes() {
if [[ -d "$LIVE_OVERLAY_UPPER_DIR" ]]; then
# Check if upper directory has any content
if [[ -n "$(find "$LIVE_OVERLAY_UPPER_DIR" -mindepth 1 -maxdepth 1 2>/dev/null)" ]]; then
return 0
fi
fi
return 1
}
# Create layer from overlay changes
create_layer_from_overlay() {
local layer_name="$1"
local message="$2"
# Create temporary directory for layer
local temp_layer_dir="${TEMP_DIR:-/tmp/apt-layer}/live-layer-${layer_name}"
mkdir -p "$temp_layer_dir"
# Copy overlay changes to temporary directory
log_info "Copying overlay changes to temporary layer" "apt-layer"
if ! cp -a "$LIVE_OVERLAY_UPPER_DIR"/* "$temp_layer_dir/" 2>/dev/null; then
log_error "Failed to copy overlay changes" "apt-layer"
rm -rf "$temp_layer_dir"
return 1
fi
# Create ComposeFS layer
log_info "Creating ComposeFS layer from overlay changes" "apt-layer"
if ! create_composefs_layer "$temp_layer_dir" "$layer_name" "$message"; then
log_error "Failed to create ComposeFS layer" "apt-layer"
rm -rf "$temp_layer_dir"
return 1
fi
# Clean up temporary directory
rm -rf "$temp_layer_dir"
return 0
}
# Create ComposeFS layer from directory
create_composefs_layer() {
local source_dir="$1"
local layer_name="$2"
local message="$3"
# Try real mkcomposefs binary first
if command -v mkcomposefs >/dev/null 2>&1; then
# Create object store directory (same directory as layer)
local object_store_dir=$(dirname "$layer_name")
mkdir -p "$object_store_dir"
if mkcomposefs "$source_dir" "$layer_name" --digest-store="$object_store_dir"; then
log_success "ComposeFS layer created using mkcomposefs" "apt-layer"
return 0
fi
fi
# Fallback to composefs-alternative
if command -v composefs-alternative >/dev/null 2>&1; then
if composefs-alternative create-layer "$source_dir" "$layer_name" "$message"; then
return 0
fi
fi
# Fallback: create simple squashfs layer
local layer_file="${BUILD_DIR:-/var/lib/apt-layer/build}/${layer_name}.squashfs"
mkdir -p "$(dirname "$layer_file")"
if mksquashfs "$source_dir" "$layer_file" -comp "${SQUASHFS_COMPRESSION:-xz}" -b "${SQUASHFS_BLOCK_SIZE:-1M}"; then
log_success "Created squashfs layer: $layer_file" "apt-layer"
return 0
else
log_error "Failed to create squashfs layer" "apt-layer"
return 1
fi
}
# Rollback live overlay changes
rollback_live_overlay() {
log_info "Rolling back live overlay changes" "apt-layer"
# Check if overlay is active
if ! is_live_overlay_active; then
log_error "Live overlay is not active" "apt-layer"
return 1
fi
# Stop overlay (this will discard changes)
if stop_live_overlay; then
log_success "Live overlay changes rolled back successfully" "apt-layer"
return 0
else
log_error "Failed to rollback live overlay changes" "apt-layer"
return 1
fi
}
# List packages installed in live overlay
list_live_overlay_packages() {
log_info "Listing packages installed in live overlay" "apt-layer"
if [[ -f "$LIVE_OVERLAY_PACKAGE_LOG" ]]; then
if [[ -s "$LIVE_OVERLAY_PACKAGE_LOG" ]]; then
echo "=== Packages Installed in Live Overlay ==="
cat "$LIVE_OVERLAY_PACKAGE_LOG"
echo ""
else
log_info "No packages installed in live overlay" "apt-layer"
fi
else
log_info "No package log found" "apt-layer"
fi
}
# Clean live overlay
clean_live_overlay() {
log_info "Cleaning live overlay" "apt-layer"
# Stop overlay if active
if is_live_overlay_active; then
stop_live_overlay
fi
# Clean up overlay directories
rm -rf "$LIVE_OVERLAY_UPPER_DIR"/* "$LIVE_OVERLAY_WORK_DIR"/* 2>/dev/null
# Clean up package log
rm -f "$LIVE_OVERLAY_PACKAGE_LOG"
# Remove state file
rm -f "$LIVE_OVERLAY_STATE_FILE"
log_success "Live overlay cleaned successfully" "apt-layer"
}
# =============================================================================
# INTEGRATION FUNCTIONS
# =============================================================================
# Initialize live overlay system on script startup
init_live_overlay_on_startup() {
# Only initialize if not already done
if [[ ! -d "$LIVE_OVERLAY_DIR" ]]; then
init_live_overlay_system
fi
}
# Cleanup live overlay on script exit
cleanup_live_overlay_on_exit() {
# Only cleanup if overlay is active and no processes are using it
if is_live_overlay_active && ! check_active_processes; then
log_info "Cleaning up live overlay on exit" "apt-layer"
stop_live_overlay
fi
}
# Register cleanup function
trap cleanup_live_overlay_on_exit EXIT