Skip to content

Instantly share code, notes, and snippets.

@SkeLLLa
Created April 7, 2025 17:35
Show Gist options
  • Save SkeLLLa/f0b14531c011788969bae6d44f0c5438 to your computer and use it in GitHub Desktop.
Save SkeLLLa/f0b14531c011788969bae6d44f0c5438 to your computer and use it in GitHub Desktop.
Import script for *arr apps to utilize hardlinking with mergerfs
#!/bin/bash
# Universal import script for *arr applications with MergerFS
# Works with Sonarr, Radarr, Lidarr, and Readarr
# Ensures hardlinking between downloads and destination directories
set -e # Exit on any error
# Get parameters from the *arr application
SOURCE_PATH="$1"
TARGET_PATH="$2"
# Determine which application is calling the script based on environment variables
if [[ -n "$sonarr_eventtype" ]]; then
APP_NAME="Sonarr"
elif [[ -n "$radarr_eventtype" ]]; then
APP_NAME="Radarr"
elif [[ -n "$lidarr_eventtype" ]]; then
APP_NAME="Lidarr"
elif [[ -n "$readarr_eventtype" ]]; then
APP_NAME="Readarr"
else
# Try to guess based on the path
if [[ "$TARGET_PATH" == */series/* ]]; then
APP_NAME="Sonarr"
elif [[ "$TARGET_PATH" == */movies/* ]]; then
APP_NAME="Radarr"
elif [[ "$TARGET_PATH" == */music/* ]]; then
APP_NAME="Lidarr"
elif [[ "$TARGET_PATH" == */books/* ]]; then
APP_NAME="Readarr"
else
APP_NAME="Unknown"
fi
fi
# Log file for debugging
LOG_FILE="/tmp/${APP_NAME,,}_import.log"
# Function to log messages
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $APP_NAME - $1" | tee -a "$LOG_FILE"
}
# Check if parameters are provided
if [ -z "$SOURCE_PATH" ] || [ -z "$TARGET_PATH" ]; then
log "Error: Source or target path not provided"
log "Usage: $0 <source_path> <target_path>"
exit 1
fi
log "Starting import from $SOURCE_PATH to $TARGET_PATH"
# Find the actual physical location of the source file using getfattr
SOURCE_BASE_PATH=$(getfattr -n user.mergerfs.basepath --only-values "$SOURCE_PATH" 2>/dev/null || echo "")
if [ -z "$SOURCE_BASE_PATH" ]; then
log "Error: Could not determine the physical location of the source file"
log "Make sure the getfattr utility is installed and the file has the mergerfs attribute"
exit 1
fi
log "Physical base path: $SOURCE_BASE_PATH"
# Get the relative path from the mergerfs mount point
RELATIVE_PATH=${SOURCE_PATH#/media/storage/}
log "Relative path: $RELATIVE_PATH"
# Construct the full physical source path
PHYSICAL_SOURCE_PATH="${SOURCE_BASE_PATH}/${RELATIVE_PATH}"
log "Full physical source path: $PHYSICAL_SOURCE_PATH"
# Modify the target path to be on the same physical drive
RELATIVE_TARGET_PATH=${TARGET_PATH#/media/storage/}
PHYSICAL_TARGET_PATH="${SOURCE_BASE_PATH}/${RELATIVE_TARGET_PATH}"
log "Physical target path: $PHYSICAL_TARGET_PATH"
# Create the directory structure for the physical target
PHYSICAL_TARGET_DIR=$(dirname "$PHYSICAL_TARGET_PATH")
mkdir -p "$PHYSICAL_TARGET_DIR"
# Create a hardlink from physical source to the physical target
log "Creating hardlink from $PHYSICAL_SOURCE_PATH to $PHYSICAL_TARGET_PATH"
if ln "$PHYSICAL_SOURCE_PATH" "$PHYSICAL_TARGET_PATH"; then
log "Hardlink created successfully"
# Handle associated files based on the application type
SOURCE_DIR=$(dirname "$PHYSICAL_SOURCE_PATH")
SOURCE_FILENAME=$(basename "$PHYSICAL_SOURCE_PATH")
SOURCE_NAME="${SOURCE_FILENAME%.*}"
# Define file extensions to look for based on application
case "$APP_NAME" in
"Sonarr")
# Video and subtitle files
EXTENSIONS=(".srt" ".sub" ".idx" ".ass" ".ssa" ".vtt" ".nfo")
;;
"Radarr")
# Video and subtitle files
EXTENSIONS=(".srt" ".sub" ".idx" ".ass" ".ssa" ".vtt" ".nfo")
;;
"Lidarr")
# Audio and metadata files
EXTENSIONS=(".cue" ".log" ".jpg" ".jpeg" ".png" ".pdf" ".nfo")
;;
"Readarr")
# E-book related files
EXTENSIONS=(".opf" ".jpg" ".jpeg" ".png" ".pdf")
;;
*)
# Default - try common extensions
EXTENSIONS=(".srt" ".sub" ".idx" ".jpg" ".jpeg" ".png" ".nfo")
;;
esac
# Look for associated files with the same base name
for EXT in "${EXTENSIONS[@]}"; do
for ASSOC_FILE in "$SOURCE_DIR/$SOURCE_NAME"*"$EXT"; do
if [ -f "$ASSOC_FILE" ]; then
ASSOC_FILENAME=$(basename "$ASSOC_FILE")
PHYSICAL_TARGET_ASSOC="${PHYSICAL_TARGET_DIR}/${ASSOC_FILENAME}"
log "Found associated file: $ASSOC_FILE"
log "Creating hardlink for associated file to $PHYSICAL_TARGET_ASSOC"
ln "$ASSOC_FILE" "$PHYSICAL_TARGET_ASSOC" || log "Warning: Failed to hardlink associated file $ASSOC_FILE"
fi
done
done
# Special case for multi-file media (like music albums or book series)
if [[ "$APP_NAME" == "Lidarr" || "$APP_NAME" == "Readarr" ]]; then
# If the source is a directory, hardlink all files inside it
if [ -d "$PHYSICAL_SOURCE_PATH" ]; then
log "Source is a directory, hardlinking all files inside"
find "$PHYSICAL_SOURCE_PATH" -type f -print0 | while IFS= read -r -d '' FILE; do
REL_PATH="${FILE#$PHYSICAL_SOURCE_PATH/}"
TARGET_FILE="$PHYSICAL_TARGET_PATH/$REL_PATH"
TARGET_DIR=$(dirname "$TARGET_FILE")
mkdir -p "$TARGET_DIR"
ln "$FILE" "$TARGET_FILE" || log "Warning: Failed to hardlink $FILE"
done
fi
fi
log "Import completed successfully"
exit 0
else
log "Error: Failed to create hardlink"
exit 1
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment