diff --git a/git_pull_execute.sh b/git_pull_execute.sh index 469be96..664a215 100755 --- a/git_pull_execute.sh +++ b/git_pull_execute.sh @@ -52,6 +52,14 @@ # acquire_lock — prevents concurrent pulls # detect_hosts() — MY_ID required to build correct sparse checkout rules # Docker check — Gitea container status probed before any SSH attempt +# Self re-exec — a pull that replaces this file restarts the run once, so the steps after +# the sync are the pulled ones rather than the ones already in memory +# +# The re-exec is the non-obvious one. This script lives in the repo it pulls, and git installs +# an updated file by rename — the running bash holds the old inode and finishes the run on the +# old logic. Nothing looks wrong; the change simply takes effect one run late. Bounded by +# VV_PULL_REEXECED so it can restart at most once, skipped entirely when the checksum is +# unchanged, and the lock is released by hand first because exec does not fire EXIT traps. # # ============================================================================================== # CONFIGURATION @@ -86,6 +94,12 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# This script lives inside the repo it pulls, so a pull can replace it mid-run. Recorded before +# anything happens; compared again once the pull lands. See the re-exec block after the sync. +_VV_SELF="$SCRIPT_DIR/$(basename "${BASH_SOURCE[0]}")" +_VV_SELF_ARGS=("$@") +_VV_SELF_SUM="$(md5sum "$_VV_SELF" 2>/dev/null | cut -d' ' -f1)" + # Root level script — load_config.sh is in the same directory source "$SCRIPT_DIR/load_config.sh" @@ -284,6 +298,32 @@ else fi fi + # ── Re-exec if the pull replaced this script ───────────────────────────── + # + # git installs an updated file by rename, so the running bash keeps reading the inode it + # started with. That is the benign half — no torn parse, no garbage — but it means every + # step below this line runs the version that was on disk when the run began, not the one + # just pulled. Observed 2026-08-08: a pull carrying a fix to the conf-upgrade call executed + # the old call anyway, and the fix only took effect on the following run. + # + # Re-exec makes the pull self-applying. Guarded three ways: + # VV_PULL_REEXECED — set before exec, so the new image can never re-exec again. One + # restart per run, whatever the checksums say. + # checksum compare — no change means no restart, so the common case costs one md5sum. + # lock released — exec keeps the PID but does NOT fire the EXIT trap, so the lock file + # would survive into the new image, which would then find a live PID + # holding its own name and exit 1 under strict mode. Dropped by hand + # first; the new image re-acquires it immediately. + if [[ -z "${VV_PULL_REEXECED:-}" ]]; then + _vv_self_now="$(md5sum "$_VV_SELF" 2>/dev/null | cut -d' ' -f1)" + if [[ -n "$_vv_self_now" && -n "$_VV_SELF_SUM" && "$_vv_self_now" != "$_VV_SELF_SUM" ]]; then + echo " Pull updated this script — restarting so the rest of the run uses it" + _release_all_locks + export VV_PULL_REEXECED=1 + exec bash "$_VV_SELF" "${_VV_SELF_ARGS[@]}" + fi + fi + # ── Permissions ────────────────────────────────────────────────────────── echo "" echo "━━━ $ICON_GEAR Permissions ━━━"