1 contributor
#!/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 <UUID>"
echo "To list configured devices: autonas list"
fi
return 0
}
# Mark that core library has been loaded
AUTONAS_CORE_LOADED=true