diff --git a/scripts/deploy-to-target.sh b/scripts/deploy-to-target.sh index 69824a6d..365b68fb 100755 --- a/scripts/deploy-to-target.sh +++ b/scripts/deploy-to-target.sh @@ -564,6 +564,41 @@ fi # Sync code section_start + +# GUARD (2026-07-31 incident): the rsync below is `--delete`, and TARGET_DIR +# (/home/archipelago/archy) is a SYMLINK to /home/archipelago/Projects/archy on +# archi-dev-box — which is this same machine over loopback SSH. Deploying from +# the main checkout is a harmless no-op (source and destination resolve to the +# same directory), but deploying from anywhere else on this host — a git +# worktree under .claude/worktrees/, a copy, a subdirectory — makes rsync mirror +# that source ONTO the main checkout and delete everything not present in it. +# That is exactly what happened: ~1810 tracked files deleted, the deploying +# worktree destroyed mid-run, a running dev server killed, and two concurrent +# sessions' uncommitted work lost permanently. +# +# Refuse the deploy when source and destination are on the same host AND either +# path contains the other. Identical paths are allowed (the normal local case). +_LOCAL_SRC="$(readlink -f "$PROJECT_DIR")" +_REMOTE_ID="$(ssh $SSH_OPTS "$TARGET_HOST" 'cat /etc/machine-id 2>/dev/null' 2>/dev/null || true)" +_LOCAL_ID="$(cat /etc/machine-id 2>/dev/null || true)" +if [ -n "$_REMOTE_ID" ] && [ "$_REMOTE_ID" = "$_LOCAL_ID" ]; then + _REMOTE_DST="$(ssh $SSH_OPTS "$TARGET_HOST" "readlink -f '$TARGET_DIR'" 2>/dev/null || true)" + if [ -n "$_REMOTE_DST" ] && [ "$_REMOTE_DST" != "$_LOCAL_SRC" ]; then + case "$_LOCAL_SRC/" in + "$_REMOTE_DST"/*) + echo "FATAL: refusing to deploy. Source '$_LOCAL_SRC' is INSIDE the deploy destination '$_REMOTE_DST' on this same host." >&2 + echo " 'rsync --delete' would delete the destination's contents (including this source) — the 2026-07-31 data-loss incident." >&2 + echo " Run this script from the main checkout ($_REMOTE_DST), not from a worktree or copy." >&2 + exit 1 ;; + esac + case "$_REMOTE_DST/" in + "$_LOCAL_SRC"/*) + echo "FATAL: refusing to deploy. Destination '$_REMOTE_DST' is INSIDE the source '$_LOCAL_SRC' on this same host." >&2 + exit 1 ;; + esac + fi +fi + progress "Syncing code" rsync -avz --delete \ -e "ssh $SSH_OPTS" \