stop deploy.sh from pulling a new copy of itself out from under bash

bash اسکریپت را یک‌جا نمی‌خواند؛ حین اجرا از روی دیسک ادامه می‌دهد. پس وقتی
`git pull` همین فایل را عوض می‌کند، ادامه‌ی اجرا مخلوطی از نسخه‌ی قدیم و جدید
است. یک بار همین اتفاق افتاد و گران تمام شد: نسخه‌ی قدیمی که در حال اجرا بود
سرویسِ تیمِ دیگری را ری‌استارت کرد، در حالی که نسخه‌ی اصلاح‌شده همان لحظه روی
دیسک نشسته بود.

حالا امضای فایل قبل و بعدِ pull مقایسه می‌شود و اگر عوض شده باشد، نسخه‌ی تازه
از اول اجرا می‌شود (با SKIP_PULL=1 و یک نگهبان در برابرِ حلقه‌ی بی‌پایان).

و یک حفره‌ی کوچک‌ترِ هم‌خانواده: اگر زمانِ بالا آمدنِ سرویس اصلاً خوانده نشود،
دیگر موفقیت فرض نمی‌شود. «نمی‌دانم» همان چیزی بود که دو روز پنهانش کرد.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 00:41:11 +03:30
parent 700b2e4c7f
commit 6ec82081b5

View File

@@ -42,7 +42,26 @@ fi
# ─── ۱. کدِ تازه ────────────────────────────────────────────────────────────
if [ "${SKIP_PULL:-0}" != "1" ]; then
step "گرفتنِ کدِ تازه"
# امضای خودِ این فایل قبل از pull. توضیحش پایین‌تر.
SELF_BEFORE="$(cksum "$0" 2>/dev/null || true)"
git pull --ff-only || fail "git pull نشد. اگر تغییرِ محلی دارید اول تعیین تکلیفش کنید."
# اگر همین اسکریپت در pull عوض شد، نسخه‌ی تازه از اول اجرا می‌شود.
#
# چرا لازم است: bash اسکریپت را یک‌جا نمی‌خواند، حین اجرا از روی دیسک ادامه
# می‌دهد. پس وقتی pull همین فایل را زیر پای خودش عوض می‌کند، ادامه‌ی اجرا
# مخلوطی از نسخه‌ی قدیم و جدید می‌شود. یک بار همین اتفاق افتاد: نسخه‌ی قدیمی
# که هنوز در حال اجرا بود سرویسِ تیمِ دیگری را ری‌استارت کرد، در حالی که
# نسخه‌ی اصلاح‌شده همان لحظه روی دیسک نشسته بود.
#
# DEPLOY_REEXEC جلوی حلقه‌ی بی‌پایان را می‌گیرد: نسخه‌ی تازه دیگر خودش را
# صدا نمی‌زند چون بعد از pullِ دوم دیگر چیزی عوض نمی‌شود.
if [ "${DEPLOY_REEXEC:-0}" != "1" ] \
&& [ -n "$SELF_BEFORE" ] \
&& [ "$SELF_BEFORE" != "$(cksum "$0" 2>/dev/null || true)" ]; then
printf ' این اسکریپت خودش به‌روز شد؛ نسخه‌ی تازه از اول اجرا می‌شود.\n'
DEPLOY_REEXEC=1 SKIP_PULL=1 exec "$0" "$@"
fi
else
step "گرفتنِ کد رد شد (SKIP_PULL=1)"
fi
@@ -175,7 +194,12 @@ if [ -n "$SERVICE" ]; then
BEFORE="$(systemctl show "$SERVICE" -p ActiveEnterTimestamp --value 2>/dev/null || true)"
if sudo systemctl restart "$SERVICE"; then
AFTER="$(systemctl show "$SERVICE" -p ActiveEnterTimestamp --value 2>/dev/null || true)"
if [ -n "$BEFORE" ] && [ "$BEFORE" = "$AFTER" ]; then
if [ -z "$BEFORE" ] || [ -z "$AFTER" ]; then
# نتوانستیم ثابت کنیم ری‌استارت شده. «نمی‌دانم» را موفقیت حساب نمی‌کنیم —
# همین اشتباه بود که دو روز پنهان ماند.
warn "زمانِ بالا آمدنِ $SERVICE خوانده نشد، پس ری‌استارت تأیید نشد.
خودتان بررسی کنید: systemctl status $SERVICE"
elif [ "$BEFORE" = "$AFTER" ]; then
warn "دستورِ ری‌استارت خطا نداد ولی سرویس بالا نیامد (هنوز از $BEFORE در حال اجراست).
یعنی کدِ جدید بار نشده. لاگ: sudo journalctl -u $SERVICE -n 50 --no-pager"
else