556 lines
18 KiB
Bash
556 lines
18 KiB
Bash
#!/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
|