#!/bin/bash # AutoNAS Core Library # Shared business logic for all AutoNAS components # This library contains all core functions to eliminate duplication # Load default configuration if [ -f "/etc/default/autonas" ]; then source "/etc/default/autonas" fi # Set defaults if not loaded from config AUTONAS_DEBUG="${AUTONAS_DEBUG:-false}" AUTONAS_LOG_LEVEL="${AUTONAS_LOG_LEVEL:-info}" # Global configuration CONFIG_FILE="/etc/pve/autonas/disks.conf" MOUNT_BASE="/mnt/autonas" # ============================================================================ # LOGGING FUNCTIONS # ============================================================================ # Function to log messages with debug support log_message() { local message="$1" local priority="${2:-info}" local context="${3:-${LOG_TAG:-autonas}}" # Skip debug messages unless debug mode is enabled if [ "$priority" = "debug" ] && [ "$AUTONAS_DEBUG" != "true" ]; then return 0 fi # Log to syslog with facility local0 and specified priority logger -p "local0.$priority" -t "$context" "$message" # Also log to stderr if debug mode is enabled if [ "$AUTONAS_DEBUG" = "true" ]; then echo "[$context] [$priority] $message" >&2 fi # Also echo to stdout/stderr for interactive use if [ -t 1 ]; then echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" fi } # Function for debug logging (convenience function) debug_log() { log_message "$1" "debug" "${2:-${LOG_TAG:-autonas}}" } # ============================================================================ # CONFIGURATION FUNCTIONS # ============================================================================ # Function to get disk configuration by UUID get_disk_config() { local uuid="$1" grep "^${uuid}:" "$CONFIG_FILE" 2>/dev/null } # Function to parse configuration line parse_config() { local config="$1" IFS=':' read -r uuid name ip interface mount_point nfs_options <<< "$config" echo "$uuid" "$name" "$ip" "$interface" "$mount_point" "$nfs_options" } # Function to get configuration for a specific UUID (handles systemd escaping) get_config() { local uuid="$1" if [ -z "$uuid" ]; then return 1 fi # Un-escape systemd-escaped UUID (e.g., 8765\x2d4321 -> 8765-4321) local unescaped_uuid unescaped_uuid=$(echo "$uuid" | sed 's/\\x2d/-/g') # Find configuration line for UUID (try both escaped and unescaped) grep "^${unescaped_uuid}:" "$CONFIG_FILE" 2>/dev/null || grep "^${uuid}:" "$CONFIG_FILE" 2>/dev/null } # Function to get configuration for a specific name get_config_by_name() { local name="$1" if [ -z "$name" ]; then return 1 fi # Find configuration line by name (second field) grep ":${name}:" "$CONFIG_FILE" 2>/dev/null } # Function to get configuration for a specific mount point get_config_by_mount_point() { local mount_point="$1" if [ -z "$mount_point" ] || [ ! -f "$CONFIG_FILE" ]; then return 1 fi awk -F':' -v target="$mount_point" ' $1 !~ /^[[:space:]]*#/ && $5 == target { print; exit } ' "$CONFIG_FILE" 2>/dev/null } # Resolve an entry identifier as UUID, name, or mount point resolve_config_identifier() { local identifier="$1" if [ -z "$identifier" ]; then return 1 fi # Try UUID first (supports escaped UUIDs from systemd), then name, then mount point. get_config "$identifier" \ || get_config_by_name "$identifier" \ || get_config_by_mount_point "$identifier" } # ============================================================================ # VALIDATION FUNCTIONS # ============================================================================ # Function to validate UUID format validate_uuid() { local uuid="$1" # UUID can be standard format (8-4-4-4-12) or shorter formats used by some filesystems if [[ "$uuid" =~ ^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$ ]] || \ [[ "$uuid" =~ ^[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}$ ]] || \ [[ "$uuid" =~ ^[0-9A-Fa-f]{8}$ ]]; then return 0 fi return 1 } # Function to validate disk name validate_disk_name() { local name="$1" # Check length (max 50 characters) if [[ ${#name} -gt 50 ]]; then return 1 fi # Check if it starts with letter or number if [[ ! "$name" =~ ^[a-zA-Z0-9] ]]; then return 1 fi # Check allowed characters: letters, numbers, hyphens, underscores, at symbol if [[ ! "$name" =~ ^[a-zA-Z0-9_@-]+$ ]]; then return 1 fi # Check for reserved names local reserved_names=("root" "home" "tmp" "var" "usr" "etc" "proc" "sys" "dev" "boot" "mnt" "media" "opt" "srv") for reserved in "${reserved_names[@]}"; do if [[ "$name" == "$reserved" ]]; then return 1 fi done return 0 } # Function to check if UUID exists in configuration check_uuid_exists_in_config() { local uuid="$1" grep -q "^${uuid}:" "$CONFIG_FILE" 2>/dev/null } # Function to list existing disk names (helper for duplicate detection) list_existing_disk_names() { if [[ -f "$CONFIG_FILE" ]]; then echo "Existing disk names:" while IFS=':' read -r uuid name rest; do if [[ -n "$uuid" && -n "$name" ]]; then echo " • $name (UUID: ${uuid:0:8}...)" fi done < "$CONFIG_FILE" fi } # Function to check if disk name already exists in configuration check_disk_name_exists() { local name="$1" if [[ -f "$CONFIG_FILE" ]]; then # Check if name exists as second field in any configuration line grep -q ":${name}:" "$CONFIG_FILE" 2>/dev/null fi } # Function to validate disk name (includes duplicate check) validate_disk_name_complete() { local name="$1" local uuid="$2" # Optional: exclude current UUID from duplicate check # Run basic validation first if ! validate_disk_name "$name"; then return 1 fi # Check for duplicate names if check_disk_name_exists "$name"; then # If UUID provided, check if this name belongs to the same UUID (allow updates) if [[ -n "$uuid" ]] && [[ -f "$CONFIG_FILE" ]]; then local existing_uuid=$(grep ":${name}:" "$CONFIG_FILE" | cut -d':' -f1) if [[ "$existing_uuid" == "$uuid" ]]; then # Same UUID, same name - this is an update, allow it return 0 fi fi # Different UUID or no UUID provided - this is a duplicate return 2 # Special return code for duplicate names fi return 0 } # Function to check if UUID exists in system check_uuid_exists() { local uuid="$1" blkid | grep -q "$uuid" } # Function to check if disk is already mounted at AutoNAS location is_disk_mounted() { local uuid="$1" local mount_point="$2" # Check if already mounted at the correct location if mount | grep -q "UUID=$uuid.*$mount_point"; then return 0 # Already mounted correctly fi # Check if mounted elsewhere if mount | grep -q "UUID=$uuid"; then return 1 # Mounted elsewhere fi return 2 # Not mounted } # ============================================================================ # DEVICE FUNCTIONS # ============================================================================ # Function to get device information get_device_info() { local uuid="$1" # Find device by UUID using blkid local device_line device_line=$(blkid | grep "UUID=\"$uuid\"") if [[ -z "$device_line" ]]; then return 1 fi local device=$(echo "$device_line" | cut -d: -f1) local blkid_info=$(echo "$device_line" | cut -d: -f2-) echo "device:$device" echo "$blkid_info" return 0 } # ============================================================================ # NETWORK FUNCTIONS # ============================================================================ # Function to check if network interface exists interface_exists() { local interface="$1" ip link show "$interface" >/dev/null 2>&1 } # Function to check if interface is configured for AutoNAS interface_has_autonas_config() { local interface="$1" grep -q ":${interface}:" "$CONFIG_FILE" 2>/dev/null } # Function to check if IP is configured on interface is_ip_configured() { local ip="$1" local interface="$2" ip addr show "$interface" | grep -q "inet $ip/" } # Function to count disks using specific IP count_disks_using_ip() { local target_ip="$1" local count=0 while IFS=':' read -r uuid name ip interface mount_point nfs_options; do # Skip empty lines and comments [[ -z "$uuid" || "$uuid" =~ ^# ]] && continue if [[ "$ip" == "$target_ip" ]]; then # Check if this disk is currently attached (mounted) if mountpoint -q "$mount_point" 2>/dev/null; then count=$((count + 1)) fi fi done < <(grep -v '^#' "$CONFIG_FILE" 2>/dev/null | grep -v '^$') echo $count } # Function to activate IP on interface with smart retry activate_ip() { local ip="$1" local interface="$2" local max_retries=3 local retry_count=0 # Skip activation for special cases if [[ "$ip" == "IMPORT" || "$ip" == "LOCAL" ]]; then log_message "Skipping IP activation for special configuration: $ip" "debug" return 0 fi log_message "Activating IP $ip on interface $interface" "info" # Check if interface exists if ! interface_exists "$interface"; then log_message "Interface $interface does not exist - waiting for it to appear" "warning" # Wait for interface to appear (common with USB/Thunderbolt) local wait_count=0 while [ $wait_count -lt 30 ] && ! interface_exists "$interface"; do sleep 2 wait_count=$((wait_count + 1)) debug_log "Waiting for interface $interface... (${wait_count}/30)" done if ! interface_exists "$interface"; then log_message "Error: Interface $interface does not exist" "err" return 1 fi log_message "Interface $interface is now available" "info" fi # Check if IP is already configured if is_ip_configured "$ip" "$interface"; then log_message "IP $ip already configured on interface $interface" return 0 fi # Check if interface is up if ! ip link show "$interface" | grep -q "state UP"; then log_message "Bringing up interface $interface" if ! ip link set "$interface" up; then log_message "Error: Failed to bring up interface $interface" "err" return 1 fi # Wait a moment for interface to come up sleep 1 fi # Try to configure IP with retries while [ $retry_count -lt $max_retries ]; do if ip addr add "$ip/24" dev "$interface" 2>/dev/null; then log_message "Successfully activated IP $ip on interface $interface" return 0 else retry_count=$((retry_count + 1)) if [ $retry_count -lt $max_retries ]; then log_message "Failed to add IP $ip to interface $interface, retrying ($retry_count/$max_retries)" "warning" sleep 2 fi fi done log_message "Error: Failed to activate IP $ip on interface $interface" "err" return 1 } # Function to restore IPs for interface with comprehensive checking restore_interface_ips() { local interface="$1" local restored=0 local total_configs=0 log_message "Checking IP restoration needs for interface: $interface" "info" # Check if interface exists and is up if ! interface_exists "$interface"; then log_message "Interface $interface does not exist, skipping restoration" "warning" return 1 fi # Check if interface is up if ! ip link show "$interface" | grep -q "state UP"; then log_message "Interface $interface is down, waiting for it to come up..." "info" local wait_count=0 while [ $wait_count -lt 20 ] && ! ip link show "$interface" | grep -q "state UP"; do sleep 1 wait_count=$((wait_count + 1)) done if ! ip link show "$interface" | grep -q "state UP"; then log_message "Interface $interface did not come up within 20 seconds" "warning" return 1 fi fi log_message "Interface $interface is up, checking for required IP configurations" "info" while IFS= read -r line; do # Skip comments and empty lines [[ "$line" =~ ^[[:space:]]*# ]] && continue [[ -z "$line" ]] && continue # Parse configuration IFS=':' read -r uuid name ip config_interface mount_point nfs_options <<< "$line" # Check if this line is for our interface if [ "$config_interface" = "$interface" ]; then ((total_configs++)) debug_log "Found configuration: $name ($uuid) requires IP $ip on $interface" # Check if the disk is currently mounted if mountpoint -q "$mount_point" 2>/dev/null; then log_message "Disk $name is mounted, checking IP configuration" "info" # Check if IP is already configured if ! is_ip_configured "$ip" "$interface"; then log_message "IP $ip missing on interface $interface for mounted disk: $name" "warning" # Use the core activate_ip function if activate_ip "$ip" "$interface"; then log_message "Successfully restored IP $ip on interface $interface for disk $name" "info" ((restored++)) else log_message "Failed to restore IP $ip on interface $interface for disk $name" "error" fi else debug_log "IP $ip already configured on interface $interface for disk $name" fi else debug_log "Disk $name is not mounted, skipping IP configuration" fi fi done < "$CONFIG_FILE" if [ $total_configs -eq 0 ]; then debug_log "No AutoNAS configurations found for interface $interface" elif [ $restored -gt 0 ]; then log_message "Restored $restored/$total_configs IP addresses on interface $interface" "info" # Trigger NFS export refresh if we restored any IPs log_message "Refreshing NFS exports after IP restoration" "info" exportfs -ra 2>/dev/null systemctl reload nfs-kernel-server 2>/dev/null else log_message "All required IPs already configured on interface $interface" "info" fi return 0 } # Function to deactivate IP on interface with smart checking deactivate_ip() { local ip="$1" local interface="$2" # Skip deactivation for special cases if [[ "$ip" == "IMPORT" || "$ip" == "LOCAL" ]]; then log_message "Skipping IP deactivation for special configuration: $ip" "debug" return 0 fi # Check how many other disks are using this IP local usage_count usage_count=$(count_disks_using_ip "$ip") if [[ $usage_count -gt 1 ]]; then log_message "IP $ip still used by $((usage_count - 1)) other disks, keeping it active" return 0 fi log_message "Deactivating IP $ip on interface $interface" # Check if IP is configured if ! is_ip_configured "$ip" "$interface"; then log_message "IP $ip not configured on interface $interface" return 0 fi # Remove IP from interface if ip addr del "$ip/24" dev "$interface" 2>/dev/null; then log_message "Successfully deactivated IP $ip on interface $interface" return 0 else log_message "Error: Failed to deactivate IP $ip on interface $interface" "err" return 1 fi } # ============================================================================ # NFS FUNCTIONS # ============================================================================ # Function to add NFS export with AutoNAS identification add_nfs_export() { local mount_point="$1" local nfs_options="$2" local uuid="${3:-}" local name="${4:-}" # Skip NFS export for special cases if [[ "$nfs_options" == "LOCAL" ]] || [[ "$mount_point" =~ ^.*@Camera.*$ ]]; then log_message "Skipping NFS export for local/camera configuration" "debug" return 0 fi log_message "Adding NFS export for $mount_point" # Create NFS export entry local export_entry="$mount_point $nfs_options" # Check if export already exists if grep -Fxq "$export_entry" /etc/exports 2>/dev/null; then log_message "NFS export already exists: $export_entry" return 0 fi # Add identifying comment if UUID/name provided if [[ -n "$uuid" && -n "$name" ]]; then echo "# AutoNAS Export - UUID:$uuid NAME:$name" >> /etc/exports fi # Add export to /etc/exports echo "$export_entry" >> /etc/exports if [[ $? -eq 0 ]]; then log_message "Successfully added NFS export: $export_entry" # Reload exports if exportfs -ra; then log_message "NFS exports reloaded successfully" return 0 else log_message "Warning: Failed to reload NFS exports" "warning" return 1 fi else log_message "Error: Failed to add NFS export" "err" return 1 fi } # Function to remove NFS export remove_nfs_export() { local mount_point="$1" local uuid="${2:-}" local name="${3:-}" local nfs_options="${4:-}" # Skip NFS export removal for special cases if [[ "$mount_point" =~ ^.*@Camera.*$ ]] || [[ "$nfs_options" == "LOCAL" ]]; then log_message "Skipping NFS export removal for camera/local configuration" "debug" return 0 fi log_message "Removing NFS export for $mount_point" # Create temporary file without the export local temp_exports=$(mktemp) if [[ ! -f /etc/exports ]]; then log_message "No /etc/exports file found" rm -f "$temp_exports" return 0 fi # If UUID provided, use AutoNAS marker method if [[ -n "$uuid" ]]; then local skip_next=false local found_export=false # Process /etc/exports line by line while IFS= read -r line; do if [[ "$line" =~ ^#\ AutoNAS\ Export\ -\ UUID:${uuid}\ NAME: ]]; then # Found our marker comment, skip this line and the next one skip_next=true found_export=true debug_log "Found AutoNAS export marker for $name" continue elif [ "$skip_next" = true ]; then # Skip the export line that follows our marker skip_next=false debug_log "Skipping export line for $name" continue else # Keep all other lines echo "$line" >> "$temp_exports" fi done < /etc/exports if [ "$found_export" = true ]; then # Replace /etc/exports with cleaned version mv "$temp_exports" /etc/exports log_message "NFS export removed using AutoNAS marker for $name" else rm -f "$temp_exports" # Fallback to generic method grep -v "^${mount_point} " /etc/exports > "$temp_exports" mv "$temp_exports" /etc/exports log_message "NFS export removed using generic method for $mount_point" fi else # Generic removal method grep -v "^${mount_point} " /etc/exports > "$temp_exports" mv "$temp_exports" /etc/exports log_message "NFS export removed for $mount_point" fi # Reload exports if exportfs -ra; then log_message "NFS exports reloaded successfully" return 0 else log_message "Warning: Failed to reload NFS exports" "warning" return 1 fi } # ============================================================================ # STORAGE FUNCTIONS # ============================================================================ # Function to mount disk with comprehensive filesystem support mount_disk() { local uuid="$1" local mount_point="$2" local filesystem_type="${3:-}" log_message "Mounting disk with UUID $uuid to $mount_point" # Wait for device to be ready (udev sometimes triggers too early) local device_path="/dev/disk/by-uuid/$uuid" local wait_count=0 while [ ! -e "$device_path" ] && [ $wait_count -lt 10 ]; do debug_log "Waiting for device $device_path to appear..." sleep 1 wait_count=$((wait_count + 1)) done if [ ! -e "$device_path" ]; then # Try alternative method - find device by UUID using blkid local device device=$(blkid | grep "UUID=\"$uuid\"" | cut -d: -f1) if [[ -z "$device" ]]; then log_message "Error: Device with UUID $uuid not found" "err" return 1 fi device_path="$device" fi # Get the actual device name local actual_device=$(readlink -f "$device_path" 2>/dev/null || echo "$device_path") log_message "Device UUID $uuid maps to $actual_device" # Check if device is already mounted if mountpoint -q "$mount_point" 2>/dev/null; then # Check if it's mounted to the correct device local mounted_device mounted_device=$(findmnt -n -o SOURCE "$mount_point") if [[ "$mounted_device" == "$actual_device" ]]; then log_message "Disk already mounted correctly at $mount_point" return 0 else log_message "Warning: Different device mounted at $mount_point, unmounting first" "warning" umount "$mount_point" 2>/dev/null fi fi # Create mount point if it doesn't exist if [[ ! -d "$mount_point" ]]; then if mkdir -p "$mount_point"; then log_message "Created mount point directory: $mount_point" else log_message "Error: Failed to create mount point directory: $mount_point" "err" return 1 fi fi # Detect filesystem if not provided if [[ -z "$filesystem_type" ]]; then filesystem_type=$(blkid -o value -s TYPE "$actual_device" 2>/dev/null) debug_log "Detected filesystem type: $filesystem_type" fi # Mount based on filesystem type local mount_options case "$filesystem_type" in "ntfs") mount_options="-t ntfs-3g -o rw,uid=0,gid=0,umask=000" log_message "Mounting NTFS filesystem" ;; "exfat") mount_options="-t exfat -o rw,uid=0,gid=0,umask=000" log_message "Mounting exFAT filesystem" ;; "vfat"|"fat32") mount_options="-t vfat -o rw,uid=0,gid=0,umask=000" log_message "Mounting FAT32 filesystem" ;; "ext2"|"ext3"|"ext4") mount_options="-t $filesystem_type" log_message "Mounting Linux filesystem ($filesystem_type)" ;; "btrfs") mount_options="-t btrfs -o defaults,compress=zstd" log_message "Mounting BTRFS filesystem with compression" ;; "xfs") mount_options="-t xfs" log_message "Mounting XFS filesystem" ;; *) mount_options="" log_message "Unknown or no filesystem type, attempting default mount" ;; esac # Perform the mount local mount_cmd="mount $mount_options \"$actual_device\" \"$mount_point\"" debug_log "Executing: $mount_cmd" if eval $mount_cmd; then # Verify mount was successful if mountpoint -q "$mount_point"; then log_message "Disk mounted successfully at $mount_point" # Set appropriate permissions for the mount point chmod 755 "$mount_point" 2>/dev/null return 0 else log_message "Error: Mount command succeeded but mountpoint verification failed" "err" return 1 fi else # Try fallback mount by UUID for better compatibility log_message "Direct mount failed, attempting UUID fallback" "warning" if mount UUID="$uuid" "$mount_point" 2>/dev/null; then log_message "Disk mounted successfully using UUID fallback at $mount_point" return 0 else log_message "Error: Failed to mount $actual_device to $mount_point" "err" return 1 fi fi } # Function to unmount disk unmount_disk() { local mount_point="$1" log_message "Unmounting disk from $mount_point" # Check if actually mounted if ! mountpoint -q "$mount_point" 2>/dev/null; then log_message "Path $mount_point is not a mount point" return 0 fi # Attempt unmount with timeout if timeout 30 umount "$mount_point"; then log_message "Successfully unmounted $mount_point" # Remove mount point if empty if rmdir "$mount_point" 2>/dev/null; then debug_log "Removed empty mount point directory: $mount_point" else debug_log "Mount point directory not empty or removal failed: $mount_point" fi return 0 else log_message "Error: Failed to unmount $mount_point" "err" return 1 fi } # ============================================================================ # CAMERA IMPORT FUNCTIONS # ============================================================================ # Function to handle camera import handle_camera_import() { local uuid="$1" local mount_point="$2" local import_config="$3" # Parse import configuration (format: destination_path only) destination_path="$import_config" if [[ -z "$destination_path" ]]; then log_message "Error: No destination path specified for camera import" "err" return 1 fi # Always use the integrated media importer (non-configurable) script_path="/usr/local/bin/autonas-media-importer.sh" # Check if import script exists if [[ ! -x "$script_path" ]]; then log_message "Error: Camera import script not found or not executable: $script_path" "err" return 1 fi log_message "Starting camera import process for UUID: $uuid" log_message "Import destination: $destination_path" # Clean up any previous transient unit before starting a new one systemctl stop autonas-import-$uuid.service 2>/dev/null || true systemctl reset-failed autonas-import-$uuid.service 2>/dev/null || true systemctl disable autonas-import-$uuid.service 2>/dev/null || true rm -f /run/systemd/transient/autonas-import-$uuid.service 2>/dev/null || true # Run import in background using systemd-run for better process management systemd-run --no-block --unit="autonas-import-$uuid" \ /usr/local/bin/autonas-disk-handler.sh import "$uuid" "$mount_point" "$destination_path" "$script_path" || { log_message "Error: Failed to start background import process" "err" return 1 } log_message "Camera import process started successfully" return 0 } # Function to run background import run_background_import() { local uuid="$1" local mount_point="$2" local destination="$3" local script_path="${4:-/usr/local/bin/autonas-media-importer.sh}" log_message "Running background import for UUID: $uuid" "info" "autonas-import" log_message "Source: $mount_point, Destination: $destination" "info" "autonas-import" # Change to a safe directory cd /tmp || cd / # Create destination directory if it doesn't exist mkdir -p "$destination" # Ensure destination exists if [[ ! -d "$destination" ]]; then log_message "Error: Cannot access destination directory: $destination" "err" "autonas-import" return 1 fi # Run the import script log_message "Executing import script: $script_path" "info" "autonas-import" if "$script_path" "$mount_point" "$destination" --verbose; then log_message "Import completed successfully for UUID: $uuid" "info" "autonas-import" # Cleanup: unmount and remove mount point cleanup_and_unmount "$uuid" "$mount_point" return 0 else log_message "Error: Import failed for UUID: $uuid" "err" "autonas-import" cleanup_and_unmount "$uuid" "$mount_point" return 1 fi } # Function to cleanup and unmount after camera import cleanup_and_unmount() { local uuid="$1" local mount_point="$2" log_message "Cleaning up after import for UUID: $uuid" "info" "autonas-import" # Unmount the device if mountpoint -q "$mount_point" 2>/dev/null; then if umount "$mount_point"; then log_message "Successfully unmounted $mount_point" "info" "autonas-import" else log_message "Warning: Failed to unmount $mount_point" "warning" "autonas-import" fi fi # Remove mount point directory if empty if [[ -d "$mount_point" ]] && rmdir "$mount_point" 2>/dev/null; then log_message "Removed empty mount point directory: $mount_point" "info" "autonas-import" fi log_message "Cleanup completed for UUID: $uuid" "info" "autonas-import" } # ============================================================================ # MAIN WORKFLOW FUNCTIONS # ============================================================================ # Function to handle disk attachment handle_attach() { local identifier="$1" log_message "AutoNAS attach operation started for identifier: $identifier" # Get configuration by UUID, name, or mount point local config config=$(resolve_config_identifier "$identifier") if [[ -z "$config" ]]; then log_message "No configuration found for identifier: $identifier - disk will be ignored" log_message "To configure this disk, run: autonas add" return 1 fi # Parse configuration local parsed parsed=($(parse_config "$config")) local cfg_uuid="${parsed[0]}" local name="${parsed[1]}" local ip="${parsed[2]}" local interface="${parsed[3]}" local mount_point="${parsed[4]}" local nfs_options="${parsed[5]}" log_message "Handling disk attachment for UUID: $cfg_uuid" log_message "Processing disk: $name (IP: $ip, Mount: $mount_point)" # Handle different disk types if [[ "$ip" == "IMPORT" && "$interface" == "IMPORT" ]]; then # Camera import workflow: mount temporarily, import, then unmount log_message "Detected camera import configuration for UUID: $cfg_uuid" # Mount disk for import if mount_disk "$cfg_uuid" "$mount_point"; then # Start import process handle_camera_import "$cfg_uuid" "$mount_point" "$nfs_options" return $? else log_message "Error: Failed to mount camera device" "err" return 1 fi elif [[ "$ip" == "LOCAL" ]]; then # Local mount only (no network sharing) if mount_disk "$cfg_uuid" "$mount_point"; then log_message "Local disk mounted successfully at $mount_point" return 0 else return 1 fi else # Regular NFS workflow: activate IP, mount, export via NFS if activate_ip "$ip" "$interface"; then if mount_disk "$cfg_uuid" "$mount_point"; then if add_nfs_export "$mount_point" "$nfs_options" "$cfg_uuid" "$name"; then log_message "Disk attached successfully: $name" return 0 else log_message "Warning: Disk mounted but NFS export failed" "warning" return 1 fi else deactivate_ip "$ip" "$interface" return 1 fi else return 1 fi fi } # Function to handle disk detachment handle_detach() { local identifier="$1" log_message "AutoNAS detach operation started for identifier: $identifier" # Get configuration by UUID, name, or mount point local config config=$(resolve_config_identifier "$identifier") if [[ -z "$config" ]]; then log_message "No configuration found for identifier: $identifier" return 1 fi # Parse configuration local parsed parsed=($(parse_config "$config")) local cfg_uuid="${parsed[0]}" local name="${parsed[1]}" local ip="${parsed[2]}" local interface="${parsed[3]}" local mount_point="${parsed[4]}" local nfs_options="${parsed[5]}" log_message "Handling disk detachment for UUID: $cfg_uuid" log_message "Processing disk: $name (Mount: $mount_point)" # Remove NFS export (if applicable) if [[ "$ip" != "LOCAL" && "$ip" != "IMPORT" ]]; then remove_nfs_export "$mount_point" "$cfg_uuid" "$name" "$nfs_options" fi # Unmount disk if unmount_disk "$mount_point"; then # Deactivate IP (if applicable) if [[ "$ip" != "LOCAL" && "$ip" != "IMPORT" ]]; then deactivate_ip "$ip" "$interface" fi log_message "Disk detached successfully: $name" return 0 else log_message "Error: Failed to detach disk: $name" "err" return 1 fi } # Function to reload configuration handle_reload() { log_message "Reloading AutoNAS configuration" log_message "Reloading udev rules" udevadm control --reload-rules udevadm trigger --subsystem-match=block log_message "Reloading NFS exports" exportfs -ra || log_message "Warning: Failed to reload NFS exports" "warning" log_message "Reloading systemd daemon" systemctl daemon-reload || log_message "Warning: Failed to reload systemd daemon" "warning" log_message "AutoNAS configuration reload completed" return 0 } # ============================================================================ # DISPLAY FUNCTIONS # ============================================================================ # Function to show available disks show_available_disks() { echo "=== Available Storage Devices ===" echo local found_devices=0 local blkid_output # Get blkid output and process it if ! blkid_output=$(blkid 2>/dev/null); then echo "Error: Unable to scan for block devices (are you running as root?)" return 1 fi while IFS= read -r blkid_line; do [[ -z "$blkid_line" ]] && continue local device=$(echo "$blkid_line" | cut -d: -f1) # Skip if device doesn't exist (shouldn't happen with blkid, but just in case) [[ ! -b "$device" ]] && continue # Skip loop devices, ram disks, and other virtual devices case "$device" in /dev/loop*|/dev/ram*|/dev/dm-*) continue ;; esac # Extract device information local uuid=$(echo "$blkid_line" | grep -o 'UUID="[^"]*"' | cut -d'"' -f2) local label=$(echo "$blkid_line" | grep -o 'LABEL="[^"]*"' | cut -d'"' -f2) local fstype=$(echo "$blkid_line" | grep -o 'TYPE="[^"]*"' | cut -d'"' -f2) local partuuid=$(echo "$blkid_line" | grep -o 'PARTUUID="[^"]*"' | cut -d'"' -f2) # Skip if no UUID (we need UUID for AutoNAS) [[ -z "$uuid" ]] && continue found_devices=$((found_devices + 1)) # Get device size local size="" if [[ -b "$device" ]]; then size=$(lsblk -b -d -o SIZE "$device" 2>/dev/null | tail -n1) if [[ -n "$size" ]] && [[ "$size" =~ ^[0-9]+$ ]]; then # Convert to human readable size=$(numfmt --to=iec --suffix=B "$size" 2>/dev/null || echo "${size}B") fi fi # Display device information echo "Device: $device" [[ -n "$size" ]] && echo " Size: $size" echo " UUID: $uuid" [[ -n "$label" ]] && echo " Label: $label" [[ -n "$fstype" ]] && echo " Filesystem: $fstype" # Check if already configured if grep -q "^${uuid}:" "$CONFIG_FILE" 2>/dev/null; then local config_line=$(grep "^${uuid}:" "$CONFIG_FILE" 2>/dev/null) local config_name=$(echo "$config_line" | cut -d: -f2) echo " Status: ✅ Configured as '$config_name'" else echo " Status: ⚠️ Not configured" fi # Check if currently mounted local mount_info mount_info=$(findmnt -n -o TARGET "$device" 2>/dev/null) if [[ -n "$mount_info" ]]; then echo " Mount: 🟢 $mount_info" else echo " Mount: ⚪ Not mounted" fi echo done <<< "$blkid_output" if [[ $found_devices -eq 0 ]]; then echo "No storage devices with UUIDs found." echo "Make sure devices are connected and you're running as root." else echo "Found $found_devices storage device(s)" echo echo "To configure a device: autonas add " echo "To list configured devices: autonas list" fi return 0 } # Mark that core library has been loaded AUTONAS_CORE_LOADED=true