263 lines
5.7 KiB
Bash
Executable File
263 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env zsh
|
|
emulate -L zsh
|
|
setopt pipefail
|
|
|
|
program="${0:t}"
|
|
|
|
remote="${MUSICBOX_REMOTE:-NytegearMusic}"
|
|
inbox="${MUSICBOX_INBOX:-/music/inbox/Unchecked}"
|
|
delete_source=1
|
|
|
|
usage() {
|
|
cat >&2 <<EOF
|
|
usage:
|
|
$program [options] /path/to/folder
|
|
|
|
Options:
|
|
--keep-source Keep the source folder after verified transfer.
|
|
--delete-source Delete the source folder after verified transfer. Default.
|
|
--remote HOST SSH host alias/name. Default: $remote
|
|
--inbox PATH Remote inbox path. Default: $inbox
|
|
-h, --help Show this help.
|
|
|
|
Environment:
|
|
MUSICBOX_REMOTE Default remote host.
|
|
MUSICBOX_INBOX Default remote inbox path.
|
|
MUSICBOX_RSYNC Explicit rsync binary to use.
|
|
EOF
|
|
}
|
|
|
|
die() {
|
|
print -u2 "$program: $*"
|
|
exit 1
|
|
}
|
|
|
|
have_cmd() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
rsync_supports() {
|
|
local opt="$1"
|
|
"$rsync_bin" --help 2>/dev/null | grep -q -- "$opt"
|
|
}
|
|
|
|
typeset -a positional
|
|
|
|
while (( $# )); do
|
|
case "$1" in
|
|
--keep-source)
|
|
delete_source=0
|
|
shift
|
|
;;
|
|
--delete-source)
|
|
delete_source=1
|
|
shift
|
|
;;
|
|
--remote)
|
|
(( $# >= 2 )) || die "--remote requires a value"
|
|
remote="$2"
|
|
shift 2
|
|
;;
|
|
--remote=*)
|
|
remote="${1#--remote=}"
|
|
shift
|
|
;;
|
|
--inbox)
|
|
(( $# >= 2 )) || die "--inbox requires a value"
|
|
inbox="$2"
|
|
shift 2
|
|
;;
|
|
--inbox=*)
|
|
inbox="${1#--inbox=}"
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--)
|
|
shift
|
|
positional+=("$@")
|
|
break
|
|
;;
|
|
-*)
|
|
die "unknown option: $1"
|
|
;;
|
|
*)
|
|
positional+=("$1")
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if (( ${#positional} != 1 )); then
|
|
usage
|
|
exit 2
|
|
fi
|
|
|
|
[[ -n "$remote" ]] || die "remote may not be empty"
|
|
[[ -n "$inbox" ]] || die "inbox may not be empty"
|
|
|
|
have_cmd ssh || die "ssh not found"
|
|
have_cmd base64 || die "base64 not found"
|
|
have_cmd grep || die "grep not found"
|
|
|
|
if [[ -n "${MUSICBOX_RSYNC:-}" ]]; then
|
|
rsync_bin="$MUSICBOX_RSYNC"
|
|
else
|
|
rsync_bin="$(command -v rsync 2>/dev/null || true)"
|
|
fi
|
|
|
|
[[ -n "$rsync_bin" && -x "$rsync_bin" ]] || die "rsync not found"
|
|
|
|
if "$rsync_bin" --version 2>&1 | grep -qi 'openrsync'; then
|
|
die "OpenRsync detected at $rsync_bin. This script needs GNU rsync for --secluded-args and reliable remote path handling. Install GNU rsync and set MUSICBOX_RSYNC if needed."
|
|
fi
|
|
|
|
if ! "$rsync_bin" --help 2>/dev/null | grep -q -- '--secluded-args'; then
|
|
die "rsync at $rsync_bin does not support --secluded-args; refusing because remote paths may contain spaces/special characters"
|
|
fi
|
|
|
|
src="${positional[1]:A}"
|
|
|
|
[[ "$src" != "/" ]] || die "refusing to upload /"
|
|
[[ -d "$src" ]] || die "source must be an existing folder: ${positional[1]}"
|
|
|
|
name="${src:t}"
|
|
[[ -n "$name" ]] || die "could not determine folder name from source: $src"
|
|
|
|
target="${inbox%/}/$name"
|
|
marker="$target/.musicbox-rsync-in-progress"
|
|
|
|
b64() {
|
|
printf '%s' "$1" | base64 | tr -d '\n'
|
|
}
|
|
|
|
target_b64="$(b64 "$target")"
|
|
marker_b64="$(b64 "$marker")"
|
|
|
|
print "musicbox: source: $src"
|
|
print "musicbox: remote: $remote"
|
|
print "musicbox: target: $target"
|
|
|
|
ssh "$remote" "TARGET_B64='$target_b64' MARKER_B64='$marker_b64' sh -s" <<'EOF'
|
|
decode_b64() {
|
|
printf '%s' "$1" | base64 -d 2>/dev/null || printf '%s' "$1" | base64 -D
|
|
}
|
|
|
|
target="$(decode_b64 "$TARGET_B64")"
|
|
marker="$(decode_b64 "$MARKER_B64")"
|
|
|
|
if [ -e "$target" ]; then
|
|
if [ -f "$marker" ]; then
|
|
echo "musicbox: resuming interrupted transfer: $target" >&2
|
|
exit 0
|
|
else
|
|
echo "musicbox: destination already exists, refusing to overwrite: $target" >&2
|
|
exit 17
|
|
fi
|
|
fi
|
|
|
|
mkdir -p "$target" || exit 1
|
|
touch "$marker" || exit 1
|
|
EOF
|
|
|
|
prep_status=$?
|
|
if (( prep_status != 0 )); then
|
|
exit "$prep_status"
|
|
fi
|
|
|
|
typeset -a transfer_opts
|
|
transfer_opts=(
|
|
-a
|
|
-h
|
|
-v
|
|
--partial
|
|
--append-verify
|
|
--secluded-args
|
|
)
|
|
|
|
if "$rsync_bin" --help 2>/dev/null | grep -q -- '--no-i-r'; then
|
|
transfer_opts+=(--no-i-r)
|
|
fi
|
|
|
|
if "$rsync_bin" --help 2>/dev/null | grep -q -- '--info='; then
|
|
transfer_opts+=(--info=progress2,stats2,name0)
|
|
else
|
|
transfer_opts+=(--progress --stats)
|
|
fi
|
|
|
|
if "$rsync_bin" --help 2>/dev/null | grep -q -- '--outbuf='; then
|
|
transfer_opts+=(--outbuf=L)
|
|
fi
|
|
|
|
print "musicbox: transferring..."
|
|
|
|
echo "$rsync_bin" "${transfer_opts[@]}" -- \
|
|
"$src"/ \
|
|
"$remote:$target"/
|
|
|
|
"$rsync_bin" "${transfer_opts[@]}" -- \
|
|
"$src"/ \
|
|
"$remote:$target"/
|
|
|
|
rsync_status=$?
|
|
|
|
if (( rsync_status != 0 )); then
|
|
print -u2 "musicbox: transfer interrupted or failed. Re-run the same command to resume."
|
|
print -u2 "musicbox: source was NOT deleted."
|
|
exit "$rsync_status"
|
|
fi
|
|
|
|
print "musicbox: verifying destination..."
|
|
|
|
verify_output="$(
|
|
"$rsync_bin" -a -h -n -c \
|
|
--delete \
|
|
--itemize-changes \
|
|
--exclude='.musicbox-rsync-in-progress' \
|
|
--secluded-args \
|
|
-- \
|
|
"$src"/ \
|
|
"$remote:$target"/
|
|
)"
|
|
|
|
verify_status=$?
|
|
|
|
if (( verify_status != 0 )); then
|
|
print -u2 "musicbox: verification failed. Source was NOT deleted."
|
|
exit "$verify_status"
|
|
fi
|
|
|
|
if [[ -n "$verify_output" ]]; then
|
|
print -u2 "musicbox: verification found differences. Source was NOT deleted."
|
|
print -u2 "$verify_output"
|
|
exit 1
|
|
fi
|
|
|
|
ssh "$remote" "MARKER_B64='$marker_b64' sh -s" <<'EOF'
|
|
decode_b64() {
|
|
printf '%s' "$1" | base64 -d 2>/dev/null || printf '%s' "$1" | base64 -D
|
|
}
|
|
|
|
marker="$(decode_b64 "$MARKER_B64")"
|
|
rm -f -- "$marker"
|
|
EOF
|
|
|
|
cleanup_status=$?
|
|
if (( cleanup_status != 0 )); then
|
|
print -u2 "musicbox: warning: transfer verified, but failed to remove remote marker: $marker"
|
|
exit "$cleanup_status"
|
|
fi
|
|
|
|
print "musicbox: transfer verified."
|
|
|
|
if (( delete_source )); then
|
|
print "musicbox: deleting source folder: $src"
|
|
rm -rf -- "$src"
|
|
print "musicbox: done."
|
|
else
|
|
print "musicbox: source kept in place."
|
|
print "musicbox: done."
|
|
fi
|