pushing files
This commit is contained in:
23
.env
Normal file
23
.env
Normal 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
41
docker-compose.yml
Normal 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
29
monitor.sh
Executable 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
65
move_stable.sh
Executable 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
113
uploader.sh
Executable 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
|
||||||
Reference in New Issue
Block a user