scp_repo.sh 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. #!/usr/bin/env bash
  2. # bulk_scp.sh — Sync a local repo to many hosts, respecting .gitignore and continuing even if
  3. # some hosts fail. Tested on macOS Bash 3.x.
  4. #
  5. # ------------ User-tunable variables ------------
  6. LOCAL_DIR="." # Local directory you want to send
  7. REMOTE_DIR="~/exo-v2" # Destination directory on the remote machines
  8. HOSTS_FILE="hosts.json" # JSON array of hosts (["user@ip", ...])
  9. # ------------ End of user-tunable section -------
  10. set -uo pipefail # Treat unset vars as error; fail pipelines, but we handle exit codes ourselves
  11. if [ "$#" -ne 1 ]; then
  12. echo "Usage: $0 <password>" >&2
  13. exit 1
  14. fi
  15. PASSWORD="$1"
  16. # Dependency checks
  17. for cmd in sshpass jq rsync git; do
  18. if ! command -v "$cmd" >/dev/null 2>&1; then
  19. echo "Error: $cmd is required but not installed." >&2
  20. exit 1
  21. fi
  22. done
  23. # Verify hosts file exists
  24. if [ ! -f "$HOSTS_FILE" ]; then
  25. echo "Error: Hosts file '$HOSTS_FILE' not found." >&2
  26. exit 1
  27. fi
  28. # Build a temporary exclude file containing every Git‑ignored path
  29. EXCLUDE_FILE=$(mktemp)
  30. trap 'rm -f "$EXCLUDE_FILE"' EXIT
  31. if git -C "$LOCAL_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  32. git -C "$LOCAL_DIR" ls-files -z -o -i --exclude-standard \
  33. | tr '\0' '\n' > "$EXCLUDE_FILE"
  34. else
  35. # Fallback: just use top‑level .gitignore if present
  36. [ -f "$LOCAL_DIR/.gitignore" ] && cat "$LOCAL_DIR/.gitignore" > "$EXCLUDE_FILE"
  37. fi
  38. # Iterate over hosts — process substitution keeps stdin free for rsync/ssh
  39. while IFS= read -r TARGET || [ -n "$TARGET" ]; do
  40. [ -z "$TARGET" ] && continue # skip blanks
  41. echo "\n—— Syncing $LOCAL_DIR → $TARGET:$REMOTE_DIR ——"
  42. # # Ensure remote directory exists (ignore failure but report)
  43. # if ! sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no "$TARGET" "mkdir -p $REMOTE_DIR" </dev/null; then
  44. # echo "✗ Failed to create $REMOTE_DIR on $TARGET" >&2
  45. # continue # move on to next host
  46. # fi
  47. # Rsync with checksums; redirect stdin so rsync/ssh can't eat host list
  48. if sshpass -p "$PASSWORD" rsync -azc --delete --exclude-from="$EXCLUDE_FILE" \
  49. -e "ssh -o StrictHostKeyChecking=no" \
  50. "$LOCAL_DIR/" "$TARGET:$REMOTE_DIR/" </dev/null; then
  51. echo "✓ Success: $TARGET"
  52. else
  53. echo "✗ Failed: $TARGET" >&2
  54. fi
  55. done < <(jq -r '.[]' "$HOSTS_FILE")