pushing files

This commit is contained in:
RandyJC
2025-12-01 09:24:53 +01:00
commit 6ab601771c
5 changed files with 271 additions and 0 deletions

23
.env Normal file
View File

@@ -0,0 +1,23 @@
# User/permissions
PUID=99
PGID=100
TZ=UTC
# Paths on the host (Unraid shares)
INBOX=/mnt/user/photosync/inbox
DROP=/mnt/user/photosync/drop
# Immich connection
IMMICH_URL=http://immich:8080
IMMICH_API_KEY=REPLACE_ME
ALLOW_INSECURE_SSL=false
# Mover tuning
STABLE_FOR=5
SCAN_FALLBACK=60
# Uploader tuning
UPLOAD_CONCURRENCY=4
UPLOAD_SCAN_INTERVAL=5
UPLOAD_IDLE_SLEEP=3
DELETE_ON_SUCCESS=true

41
docker-compose.yml Normal file
View File

@@ -0,0 +1,41 @@
version: "3.9"
services:
photosync-mover:
image: alpine:3.19
container_name: photosync-mover
restart: unless-stopped
environment:
- TZ=${TZ:-UTC}
- INBOX=${INBOX}
- DROP=${DROP}
- STABLE_FOR=${STABLE_FOR:-5}
- SCAN_FALLBACK=${SCAN_FALLBACK:-60}
volumes:
- ${INBOX}:/inbox:rw
- ${DROP}:/drop:rw
- ./move_stable.sh:/scripts/move_stable.sh:ro
entrypoint: ["/bin/sh","-c","apk add --no-cache inotify-tools coreutils >/dev/null && chmod +x /scripts/move_stable.sh && exec /scripts/move_stable.sh"]
user: "${PUID:-1000}:${PGID:-1000}"
photosync-uploader:
image: ghcr.io/immich-app/immich-go:release
container_name: photosync-uploader
restart: unless-stopped
depends_on:
- photosync-mover
environment:
- TZ=${TZ:-UTC}
- IMMICH_URL=${IMMICH_URL}
- IMMICH_API_KEY=${IMMICH_API_KEY}
- SCAN_DIR=/drop
- SCAN_INTERVAL=${UPLOAD_SCAN_INTERVAL:-5}
- IDLE_SLEEP=${UPLOAD_IDLE_SLEEP:-3}
- CONCURRENCY=${UPLOAD_CONCURRENCY:-4}
- DELETE_ON_SUCCESS=${DELETE_ON_SUCCESS:-true}
- ALLOW_INSECURE_SSL=${ALLOW_INSECURE_SSL:-false}
volumes:
- ${DROP}:/drop:rw
- ./uploader.sh:/scripts/uploader.sh:ro
entrypoint: ["/bin/sh","-c","chmod +x /scripts/uploader.sh && exec /scripts/uploader.sh"]
user: "${PUID:-1000}:${PGID:-1000}"

29
monitor.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
set -eu
INBOX="${INBOX:-/mnt/user/photosync/inbox}"
DROP="${DROP:-/mnt/user/photosync/drop}"
LOGFILE="${LOGFILE:-/mnt/user/photosync-scripts/monitor.log}"
MOVER_CONTAINER="${MOVER_CONTAINER:-photosync-mover}"
UPLOADER_CONTAINER="${UPLOADER_CONTAINER:-photosync-uploader}"
TAIL_LINES="${TAIL_LINES:-10}"
TIMESTAMP="$(date '+%Y-%m-%d %H:%M:%S')"
echo "=== Photosync Monitor @ $TIMESTAMP ===" | tee -a "$LOGFILE"
inbox_count=$(find "$INBOX" -type f 2>/dev/null | wc -l | tr -d ' ')
drop_count=$(find "$DROP" -type f 2>/dev/null | wc -l | tr -d ' ')
echo "Inbox files: $inbox_count" | tee -a "$LOGFILE"
echo "Drop files: $drop_count" | tee -a "$LOGFILE"
for svc in "$MOVER_CONTAINER" "$UPLOADER_CONTAINER"; do
if docker ps --format '{{.Names}}' | grep -q "^$svc$"; then
echo "Container $svc: RUNNING" | tee -a "$LOGFILE"
else
echo "Container $svc: NOT RUNNING" | tee -a "$LOGFILE"
fi
done
echo "--- Last $TAIL_LINES uploader log lines ---" | tee -a "$LOGFILE"
docker logs --tail "$TAIL_LINES" "$UPLOADER_CONTAINER" 2>&1 | tee -a "$LOGFILE"
echo "" | tee -a "$LOGFILE"

65
move_stable.sh Executable file
View File

@@ -0,0 +1,65 @@
#!/bin/sh
set -eu
INBOX="${INBOX:-/inbox}"
DROP="${DROP:-/drop}"
STABLE_FOR="${STABLE_FOR:-5}" # seconds with unchanged size to consider stable
SCAN_FALLBACK="${SCAN_FALLBACK:-60}" # seconds between full scans (safety net)
echo "[mover] Watching inbox ${INBOX} -> ${DROP} (stable for ${STABLE_FOR}s)"
mkdir -p "$INBOX" "$DROP"
is_stable() {
f="$1"
[ -f "$f" ] || return 1
size_a="$(stat -c%s "$f" 2>/dev/null || echo 0)"
sleep "$STABLE_FOR"
size_b="$(stat -c%s "$f" 2>/dev/null || echo 0)"
[ "$size_a" -eq "$size_b" ]
}
move_file() {
src="$1"
[ -f "$src" ] || return 0
rel="${src#$INBOX/}"
dest_dir="$DROP/$(dirname "$rel")"
dest="$dest_dir/$(basename "$rel")"
mkdir -p "$dest_dir"
mv -f -- "$src" "$dest"
echo "[mover] Moved $(basename "$src")"
}
process_one() {
f="$1"
[ -f "$f" ] || return 0
if is_stable "$f"; then
move_file "$f"
fi
}
scan_all() {
find "$INBOX" -type f -size +0c -print0 | while IFS= read -r -d '' f; do
process_one "$f"
done
}
start_watcher() {
if command -v inotifywait >/dev/null 2>&1; then
echo "[mover] Starting inotify watcher"
inotifywait -q -m -r -e close_write,moved_to,create --format '%w%f' "$INBOX" | \
while IFS= read -r path; do
process_one "$path"
done &
echo "[mover] Watcher pid $!"
else
echo "[mover] inotifywait not available; relying on periodic scans"
fi
}
start_watcher
while :; do
scan_all
sleep "$SCAN_FALLBACK"
done

113
uploader.sh Executable file
View File

@@ -0,0 +1,113 @@
#!/bin/sh
set -eu
IMMICH_URL="${IMMICH_URL:?IMMICH_URL is required}" # e.g. http://immich:8080
IMMICH_API_KEY="${IMMICH_API_KEY:?IMMICH_API_KEY is required}"
SCAN_DIR="${SCAN_DIR:-/drop}"
DELETE_ON_SUCCESS="${DELETE_ON_SUCCESS:-true}"
SCAN_INTERVAL="${SCAN_INTERVAL:-5}"
ALLOW_INSECURE_SSL="${ALLOW_INSECURE_SSL:-false}"
CONCURRENCY="${CONCURRENCY:-4}"
IDLE_SLEEP="${IDLE_SLEEP:-3}"
case "$CONCURRENCY" in
''|*[!0-9]*) CONCURRENCY=1 ;;
esac
[ "$CONCURRENCY" -lt 1 ] && CONCURRENCY=1
echo "[uploader] Target: ${IMMICH_URL}"
echo "[uploader] Source (drop): ${SCAN_DIR} (scan every ${SCAN_INTERVAL}s, concurrency ${CONCURRENCY})"
CURL_OPTS="-sS -m 5 -o /dev/null -w %{http_code}"
[ "$ALLOW_INSECURE_SSL" = "true" ] && CURL_OPTS="$CURL_OPTS -k"
PING="${IMMICH_URL%/}/api/server-info"
wait_for_immich() {
backoff=1
max_backoff=30
last=""
while :; do
code="$(curl $CURL_OPTS "$PING" 2>/dev/null || true)"
case "$code" in
2*|3*|4*) [ "$last" != "up" ] && echo "[uploader] Immich is up (HTTP $code)."; return 0 ;;
*) [ "$last" != "down" ] && echo "[uploader] Waiting for Immich at $PING ...";;
esac
last="down"
sleep "$backoff"
[ "$backoff" -lt "$max_backoff" ] && backoff=$((backoff*2))
[ "$backoff" -gt "$max_backoff" ] && backoff="$max_backoff"
done
}
prune_empty_parents() {
dir="$(dirname "$1")"
while [ "$dir" != "$SCAN_DIR" ]; do
rmdir "$dir" 2>/dev/null || break
dir="$(dirname "$dir")"
done
}
upload_one() {
f="$1"
if immich-go upload from-folder \
--server "${IMMICH_URL}" \
--api-key "${IMMICH_API_KEY}" \
--no-ui --on-server-errors continue \
--pause-immich-jobs=false \
--recursive=false \
"$f" >/dev/null 2>&1
then
echo "[uploader] OK: $(basename "$f")"
if [ "$DELETE_ON_SUCCESS" = "true" ]; then
rm -f -- "$f" || true
prune_empty_parents "$f"
fi
else
echo "[uploader] FAIL: $(basename "$f") — will retry next loop"
fi
}
upload_batch() {
count="$#"
echo "[uploader] Found $count file(s) to upload"
running=0
pids=""
for f in "$@"; do
upload_one "$f" &
pid=$!
pids="$pids $pid"
running=$((running+1))
if [ "$running" -ge "$CONCURRENCY" ]; then
set -- $pids
pid_to_wait="$1"
pids="${pids# $pid_to_wait}"
wait "$pid_to_wait" || true
running=$((running-1))
fi
done
for pid in $pids; do
wait "$pid" || true
done
}
while :; do
wait_for_immich
file_list="$(find "$SCAN_DIR" -type f -size +0c -print0 | xargs -0 -I{} printf '%s\n' "{}")"
if [ -z "$file_list" ]; then
sleep "$IDLE_SLEEP"
continue
fi
oldifs="$IFS"
IFS='
'
set -- $file_list
IFS="$oldifs"
upload_batch "$@"
sleep "$SCAN_INTERVAL"
done