233 lines
5.1 KiB
Bash
233 lines
5.1 KiB
Bash
#!/bin/bash
|
|
|
|
# ----------------------------------------
|
|
# CONFIG
|
|
# ----------------------------------------
|
|
INCOMING="/mnt/user/Cache/Picard/incoming"
|
|
READY="/mnt/user/Cache/Picard/ready"
|
|
|
|
DEST1="/mnt/user/Cache/Syncthing/Julian/Musik/Thorsten -> Julian"
|
|
DEST2="/mnt/user/Media/Musik/[0] Navidrome"
|
|
|
|
LOG="/mnt/user/Cache/Picard/sync.log"
|
|
LOCKFILE="/mnt/user/Cache/Picard/sync.lock"
|
|
|
|
LOG_LINES=5000
|
|
|
|
DRY_RUN=false
|
|
|
|
CHOWN_USER="thorsten"
|
|
CHOWN_GROUP="users"
|
|
|
|
# ----------------------------------------
|
|
# NORMALIZE PATHS
|
|
# ----------------------------------------
|
|
normalize_path() {
|
|
local p="$1"
|
|
[[ "$p" != "/" ]] && p="${p%/}"
|
|
echo "$p"
|
|
}
|
|
|
|
INCOMING="$(normalize_path "$INCOMING")"
|
|
READY="$(normalize_path "$READY")"
|
|
DEST1="$(normalize_path "$DEST1")"
|
|
DEST2="$(normalize_path "$DEST2")"
|
|
|
|
# ----------------------------------------
|
|
# TRACKING
|
|
# ----------------------------------------
|
|
declare -A CREATED_DIRS_DEST1
|
|
declare -A CREATED_DIRS_DEST2
|
|
|
|
# ----------------------------------------
|
|
# FUNCTIONS
|
|
# ----------------------------------------
|
|
log() {
|
|
MSG="$(date '+%Y-%m-%d %H:%M:%S') | $1"
|
|
echo "$MSG"
|
|
echo "$MSG" >> "$LOG"
|
|
}
|
|
|
|
rotate_log() {
|
|
if [ -f "$LOG" ]; then
|
|
tail -n "$LOG_LINES" "$LOG" > "${LOG}.tmp" && mv "${LOG}.tmp" "$LOG"
|
|
fi
|
|
}
|
|
|
|
run_cmd() {
|
|
if [ "$DRY_RUN" = true ]; then
|
|
log "[DRY-RUN] $*"
|
|
else
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
rsync_cmd() {
|
|
if [ "$DRY_RUN" = true ]; then
|
|
rsync -a --dry-run "$@"
|
|
return 0
|
|
else
|
|
rsync -a "$@"
|
|
return $?
|
|
fi
|
|
}
|
|
|
|
# ----------------------------------------
|
|
# TRACK NEW DIRECTORIES
|
|
# ----------------------------------------
|
|
track_new_dirs() {
|
|
local BASE="$1"
|
|
local FULL="$2"
|
|
|
|
local PATH_ACCUM="$BASE"
|
|
|
|
IFS='/' read -ra PARTS <<< "${FULL#$BASE/}"
|
|
|
|
for PART in "${PARTS[@]}"; do
|
|
PATH_ACCUM="$PATH_ACCUM/$PART"
|
|
|
|
if [ ! -d "$PATH_ACCUM" ]; then
|
|
echo "$PATH_ACCUM"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# ----------------------------------------
|
|
# VERIFY DESTINATIONS
|
|
# ----------------------------------------
|
|
for DEST in "$DEST1" "$DEST2"; do
|
|
if [ ! -d "$DEST" ]; then
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') | ERROR: Destination not found -> $DEST"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# ----------------------------------------
|
|
# LOCKFILE
|
|
# ----------------------------------------
|
|
if [ -f "$LOCKFILE" ]; then
|
|
echo "Lockfile exists -> $LOCKFILE"
|
|
exit 1
|
|
fi
|
|
|
|
trap "rm -f '$LOCKFILE'" EXIT
|
|
touch "$LOCKFILE"
|
|
|
|
# ----------------------------------------
|
|
# QUICK EXIT
|
|
# ----------------------------------------
|
|
if ! find "$INCOMING" -type f -mmin +1 -print -quit | grep -q .; then
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') | No files to process -> exit"
|
|
exit 0
|
|
fi
|
|
|
|
# ----------------------------------------
|
|
# START
|
|
# ----------------------------------------
|
|
rotate_log
|
|
log "========================================"
|
|
log "START RUN (DRY_RUN=$DRY_RUN)"
|
|
log "========================================"
|
|
|
|
CHANGED=false
|
|
|
|
# ----------------------------------------
|
|
# 1. INCOMING → READY
|
|
# ----------------------------------------
|
|
log "---- MOVE: INCOMING → READY ----"
|
|
|
|
while read -r FILE; do
|
|
|
|
[ -f "$FILE" ] || continue
|
|
|
|
REL="${FILE#"$INCOMING"/}"
|
|
TARGET="$READY/$REL"
|
|
|
|
mkdir -p "$READY/$(dirname "$REL")"
|
|
|
|
log "MOVE -> $REL"
|
|
run_cmd mv "$FILE" "$TARGET"
|
|
|
|
done < <(find "$INCOMING" -type f -mmin +1)
|
|
|
|
# ----------------------------------------
|
|
# 2. READY → DESTS
|
|
# ----------------------------------------
|
|
log "---- SYNC: READY → DESTINATIONS ----"
|
|
log "DEST1: $DEST1"
|
|
log "DEST2: $DEST2"
|
|
|
|
while read -r FILE; do
|
|
|
|
[ -f "$FILE" ] || continue
|
|
|
|
REL="${FILE#"$READY"/}"
|
|
|
|
DEST1_FILE="$DEST1/$REL"
|
|
DEST2_FILE="$DEST2/$REL"
|
|
|
|
DIR1="$(dirname "$DEST1_FILE")"
|
|
DIR2="$(dirname "$DEST2_FILE")"
|
|
|
|
# track ALL new dirs
|
|
while read -r NEWDIR; do
|
|
CREATED_DIRS_DEST1["$NEWDIR"]=1
|
|
done < <(track_new_dirs "$DEST1" "$DIR1")
|
|
|
|
while read -r NEWDIR; do
|
|
CREATED_DIRS_DEST2["$NEWDIR"]=1
|
|
done < <(track_new_dirs "$DEST2" "$DIR2")
|
|
|
|
mkdir -p "$DIR1"
|
|
mkdir -p "$DIR2"
|
|
|
|
log "SYNC -> $REL"
|
|
|
|
rsync_cmd "$FILE" "$DEST1_FILE"
|
|
STATUS1=$?
|
|
|
|
rsync_cmd "$FILE" "$DEST2_FILE"
|
|
STATUS2=$?
|
|
|
|
if [[ $STATUS1 -eq 0 && $STATUS2 -eq 0 ]]; then
|
|
CHANGED=true
|
|
log "OK -> delete $REL"
|
|
run_cmd rm "$FILE"
|
|
else
|
|
log "ERROR -> $REL (dest1=$STATUS1 dest2=$STATUS2)"
|
|
fi
|
|
|
|
done < <(find "$READY" -type f 2>/dev/null)
|
|
|
|
# ----------------------------------------
|
|
# 3. FIX OWNERSHIP (PRECISION MODE)
|
|
# ----------------------------------------
|
|
log "CHANGED=$CHANGED"
|
|
|
|
if [ "$CHANGED" = true ]; then
|
|
log "FIX OWNERSHIP (state-aware)"
|
|
|
|
for DIR in "${!CREATED_DIRS_DEST1[@]}"; do
|
|
log "CHOWN DEST1 -> $DIR"
|
|
run_cmd chown -R "$CHOWN_USER:$CHOWN_GROUP" "$DIR"
|
|
done
|
|
|
|
for DIR in "${!CREATED_DIRS_DEST2[@]}"; do
|
|
log "CHOWN DEST2 -> $DIR"
|
|
run_cmd chown -R "$CHOWN_USER:$CHOWN_GROUP" "$DIR"
|
|
done
|
|
fi
|
|
|
|
# ----------------------------------------
|
|
# CLEANUP
|
|
# ----------------------------------------
|
|
if [ "$DRY_RUN" != true ]; then
|
|
find "$INCOMING" -mindepth 1 -type d -empty -delete
|
|
find "$READY" -mindepth 1 -type d -empty -delete 2>/dev/null
|
|
else
|
|
log "[DRY-RUN] Skipping cleanup"
|
|
fi
|
|
|
|
log "========================================"
|
|
log "END RUN"
|
|
log "========================================" |