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
+20
+20
View File
@@ -26,12 +26,32 @@ warn() { printf '\033[1;33m !! %s\033[0m\n' "$*"; }
cd "$REPO_DIR" cd "$REPO_DIR"
# ---- 1. code --------------------------------------------------------------- # ---- 1. code ---------------------------------------------------------------
# This script lives in the repo it updates, so `git reset --hard` rewrites the
# file bash is in the middle of reading. Bash reads a script incrementally and
# keeps a byte offset, so after the reset it resumes at that offset in the *new*
# file — silently skipping or garbling whatever the diff shifted. (That is not
# 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)" say "Fetching $BRANCH from $(git remote get-url origin)"
git fetch --prune origin git fetch --prune origin
git checkout -q "$BRANCH" git checkout -q "$BRANCH"
git reset --hard "origin/$BRANCH" git reset --hard "origin/$BRANCH"
echo "now at $(git log --oneline -1)" 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() {
local key=$1 value=$2 local key=$1 value=$2