#!/usr/bin/env bash # Minimal rsync stand-in for Windows Git Bash (tar-over-ssh). # Supports the flags used by server-dev/sync-submodules-rsync.sh: # rsync -az [--delete] [--exclude PATTERN ...] SRC/ HOST:DEST/ set -euo pipefail DELETE=false EXCLUDES=() ARGS=() while (($#)); do case "$1" in -az|-a|-z|-v|-q|-P|--progress) shift ;; --delete) DELETE=true; shift ;; --exclude) EXCLUDES+=("$2"); shift 2 ;; --exclude=*) EXCLUDES+=("${1#--exclude=}"); shift ;; -*) # ignore unknown short/long flags used by callers shift ;; *) ARGS+=("$1"); shift ;; esac done if ((${#ARGS[@]} < 2)); then echo "rsync-shim: need SRC and DEST" >&2 exit 1 fi SRC="${ARGS[0]}" DEST="${ARGS[1]}" # Git Bash /d/foo → Windows D:/foo (OpenSSH scp 용) to_win_path() { local p="$1" if [[ "${p}" =~ ^/([a-zA-Z])/(.*)$ ]]; then local drive rest drive="$(echo "${BASH_REMATCH[1]}" | tr 'a-z' 'A-Z')" rest="${BASH_REMATCH[2]}" echo "${drive}:/${rest}" else echo "${p}" fi } copy_file_remote() { local src_file="$1" local dest="$2" local win_src win_src="$(to_win_path "${src_file}")" if [[ -f "${win_src}" || -f "${src_file}" ]]; then local local_src="${src_file}" [[ -f "${win_src}" ]] && local_src="${win_src}" scp -o BatchMode=yes -q "${local_src}" "${dest}" && return 0 fi # fallback: tar stream single file local base dir base="$(basename "${src_file}")" dir="$(cd "$(dirname "${src_file}")" && pwd)" local host="${dest%%:*}" local remote="${dest#*:}" local remote_dir remote_base if [[ "${remote}" == */ ]]; then remote_dir="${remote%/}" remote_base="${base}" else remote_dir="$(dirname "${remote}")" remote_base="$(basename "${remote}")" fi ssh -o BatchMode=yes "${host}" "mkdir -p '${remote_dir}'" tar -C "${dir}" -cf - "${base}" \ | ssh -o BatchMode=yes "${host}" "tar -C '${remote_dir}' -xf - && if [[ '${remote_base}' != '${base}' ]]; then mv '${remote_dir}/${base}' '${remote_dir}/${remote_base}'; fi" } # file → file copy (overlay yml etc.) if [[ -f "${SRC}" && "${DEST}" == *:* ]]; then copy_file_remote "${SRC}" "${DEST}" exit 0 fi # normalize trailing slash semantics like rsync SRC/ SRC_DIR="${SRC%/}" if [[ ! -d "${SRC_DIR}" ]]; then # allow copying a single file into a remote dir if [[ -f "${SRC_DIR}" ]]; then copy_file_remote "${SRC_DIR}" "${DEST}" exit 0 fi echo "rsync-shim: source not found: ${SRC}" >&2 exit 1 fi if [[ "${DEST}" != *:* ]]; then echo "rsync-shim: only remote DEST (host:path) supported" >&2 exit 1 fi HOST="${DEST%%:*}" REMOTE_PATH="${DEST#*:}" REMOTE_PATH="${REMOTE_PATH%/}" TAR_EXCLUDES=() for pat in "${EXCLUDES[@]}"; do TAR_EXCLUDES+=(--exclude "${pat}") done ssh -o BatchMode=yes "${HOST}" "mkdir -p '${REMOTE_PATH}'" if $DELETE; then # wipe remote dir contents then unpack (approx --delete) # 서버 public-* 등이 root/docker 소유일 수 있어 실패해도 이어서 덮어쓴다 ssh -o BatchMode=yes "${HOST}" \ "chmod -R u+w '${REMOTE_PATH}' 2>/dev/null || true; \ find '${REMOTE_PATH}' -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true" \ || true fi # shellcheck disable=SC2086 tar -C "${SRC_DIR}" "${TAR_EXCLUDES[@]}" -cf - . \ | ssh -o BatchMode=yes "${HOST}" "tar -C '${REMOTE_PATH}' -xf -" echo "rsync-shim: synced ${SRC_DIR}/ -> ${HOST}:${REMOTE_PATH}/"