deploy: re-exec from a copy, so the script cannot rewrite itself mid-run

deploy.sh lives in the repo it updates. `git reset --hard` therefore replaced
the file bash was reading; bash keeps a byte offset into the script and resumed
at that offset in the new file, skipping whatever the diff had shifted.

That is how the previous commit's renewal hook ended up present on the server
and never executed: `grep` found it in deploy.sh, the hook directory was empty.

Stage one now updates the checkout and re-execs a copy under /tmp, which no
later `git reset` can touch; stage two does the work and removes the copy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
timka123andClaude Opus 5 committed 2026-10-19 15:11:01 +03:00
1 parent 9270bd70cb
commit 5259e027c6
1 file changed
+25 -5
+25 -5
View File
@@ -26,11 +26,31 @@ warn() { printf '\033[1;33m !! %s\033[0m\n' "$*"; }
cd "$REPO_DIR" cd "$REPO_DIR"
# ---- 1. code --------------------------------------------------------------- # ---- 1. code ---------------------------------------------------------------
say "Fetching $BRANCH from $(git remote get-url origin)" # This script lives in the repo it updates, so `git reset --hard` rewrites the
git fetch --prune origin # file bash is in the middle of reading. Bash reads a script incrementally and
git checkout -q "$BRANCH" # keeps a byte offset, so after the reset it resumes at that offset in the *new*
git reset --hard "origin/$BRANCH" # file — silently skipping or garbling whatever the diff shifted. (That is not
echo "now at $(git log --oneline -1)" # hypothetical: it is how the certbot renewal hook came to be committed, present
# on disk, and never executed.)
#
# So: stage one updates the checkout and re-execs a *copy* outside the repo,
# which nothing can rewrite underneath it. Stage two does the actual work.
if [[ ${KADENCE_DEPLOY_STAGE:-} != run ]]; then
say "Fetching $BRANCH from $(git remote get-url origin)"
git fetch --prune origin
git checkout -q "$BRANCH"
git reset --hard "origin/$BRANCH"
echo "now at $(git log --oneline -1)"
staged=$(mktemp /tmp/kadence-deploy.XXXXXX.sh)
cat "$REPO_DIR/deploy/deploy.sh" > "$staged"
chmod +x "$staged"
export KADENCE_DEPLOY_STAGE=run KADENCE_DEPLOY_STAGED="$staged"
exec "$staged" "$@"
fi
# Stage two: started from the copy, so clean it up on the way out.
trap '[[ -n ${KADENCE_DEPLOY_STAGED:-} ]] && rm -f "$KADENCE_DEPLOY_STAGED"' EXIT
# ---- 2. secrets ------------------------------------------------------------ # ---- 2. secrets ------------------------------------------------------------
setkv() { setkv() {