let a deploy repair the server instead of stopping at it

تا حالا اگر کسی روی سرور فایلی را دستی عوض کرده بود، `git pull --ff-only`
شکست می‌خورد و کلِ اسکریپت همان‌جا می‌ایستاد. چون هیچ‌کدام از مراحلِ بعدی
اجرا نمی‌شد، سرور با کدِ قدیمی می‌ماند — بی‌صدا. یک بار همین دو روز طول کشید.

حالا مرحله‌ی اول fetch می‌کند، اگر تغییرِ دستی (یا کامیتِ محلی) باشد آن را در
.deploy-backups/*.patch نگه می‌دارد، و با `git reset --hard origin/<branch>`
کنارش می‌گذارد و ادامه می‌دهد. سرور در هر وضعیتی باشد، یک اجرا دقیقاً همان
چیزی می‌شود که روی gitea است.

`git clean` عمداً زده نمی‌شود و نباید اضافه شود: db.sqlite3 و .env و
.django_secret_key ردیابی‌نشده و داخلِ همین پوشه‌اند.

با مخزنِ ساختگی هر سه حالت آزموده شد: تغییرِ دستی، درختِ تمیز، و کامیتِ محلیِ
واگرا. در هر سه، .env و db.sqlite3 دست‌نخورده ماندند.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 00:54:06 +03:30
parent 6ec82081b5
commit 8eedbdeac0
3 changed files with 67 additions and 4 deletions

View File

@@ -42,9 +42,39 @@ fi
# ─── ۱. کدِ تازه ────────────────────────────────────────────────────────────
if [ "${SKIP_PULL:-0}" != "1" ]; then
step "گرفتنِ کدِ تازه"
# امضای خودِ این فایل قبل از pull. توضیحش پایین‌تر.
# امضای خودِ این فایل قبل از به‌روزرسانی. توضیحش پایین‌تر.
SELF_BEFORE="$(cksum "$0" 2>/dev/null || true)"
git pull --ff-only || fail "git pull نشد. اگر تغییرِ محلی دارید اول تعیین تکلیفش کنید."
# شاخه‌ی فعلی؛ اگر HEAD جدا افتاده باشد main مبنا می‌شود.
BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo main)"
[ "$BRANCH" = "HEAD" ] && BRANCH=main
git fetch origin "$BRANCH" \
|| fail "به gitea وصل نشد. شبکه یا دسترسیِ مخزن را بررسی کنید."
# اگر کسی روی سرور فایلی را دستی عوض کرده، اول نسخه‌ی پشتیبان، بعد کنار
# گذاشتنش.
#
# چرا خودکار و نه توقف: قبلاً اینجا `git pull --ff-only` بود و با هر تغییرِ
# دستی کلِ دیپلوی همان‌جا می‌ایستاد — و چون بقیه‌ی مراحل اجرا نمی‌شد، سرور
# با کدِ قدیمی می‌ماند بدونِ اینکه کسی بفهمد. دقیقاً همین یک بار دو روز طول
# کشید. حالا تغییرِ دستی مانعِ استقرار نیست؛ ذخیره می‌شود و رد می‌شویم.
if ! git diff --quiet HEAD 2>/dev/null; then
mkdir -p .deploy-backups
PATCH=".deploy-backups/local-changes-$(date +%Y%m%d-%H%M%S).patch"
git diff HEAD > "$PATCH" 2>/dev/null || true
warn "روی سرور تغییرِ دستی پیدا شد. در $PATCH ذخیره شد و کنار گذاشته می‌شود.
برای دیدنش: cat $PATCH
اگر لازم بود: git apply $PATCH"
fi
# ⚠️ اینجا عمداً `git clean` زده نمی‌شود و هرگز هم نباید زده شود:
# db.sqlite3 و .env و .django_secret_key هیچ‌کدام در گیت نیستند و داخلِ همین
# پوشه‌اند. یک `git clean -fd` یعنی نابودیِ دیتابیس، تنظیمات، و کلیدی که
# همه‌ی نشست‌های ورود با آن امضا شده‌اند. reset --hard فقط فایل‌های ردیابی‌شده
# را برمی‌گرداند و به این‌ها کار ندارد.
git reset --hard "origin/$BRANCH" \
|| fail "برگرداندن به origin/$BRANCH نشد؛ هیچ تغییری اعمال نشد."
# اگر همین اسکریپت در pull عوض شد، نسخه‌ی تازه از اول اجرا می‌شود.
#