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:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -14,3 +14,6 @@ data/textbooks/*
|
||||
|
||||
# خروجیِ collectstatic — تولیدی است، در هر دیپلوی از نو ساخته میشود.
|
||||
staticfiles/
|
||||
|
||||
# نسخهی پشتیبانِ تغییرهای دستیِ روی سرور که deploy.sh کنار میگذارد.
|
||||
.deploy-backups/
|
||||
|
||||
34
CLAUDE.md
34
CLAUDE.md
@@ -127,8 +127,38 @@ colourless.
|
||||
|
||||
## Operations
|
||||
|
||||
`./deploy.sh` is the whole release: pull, install, migrate, collectstatic, restart, then
|
||||
it polls `/api/health` and prints the likely cause if the service does not come back.
|
||||
**The whole release, as one line:**
|
||||
|
||||
```bash
|
||||
ssh -t administrator@78.38.35.249 'cd ~/mathapp-v2 && ./deploy.sh; sudo systemctl restart zareian-mathapp'
|
||||
```
|
||||
|
||||
It asks for the password twice — once for ssh, once for sudo. **Wait for each prompt**;
|
||||
pasting several lines at once feeds the queued text to the password prompt and locks you
|
||||
out for three tries.
|
||||
|
||||
The trailing restart is separate because `sudo` inside `deploy.sh` cannot prompt for a
|
||||
password. The script no longer pretends that worked: it compares the service's start time
|
||||
before and after and exits non-zero when the restart did not happen, so a half-finished
|
||||
deploy can never again look like a successful one. Giving `administrator` a `NOPASSWD`
|
||||
sudoers line for just `systemctl restart zareian-mathapp` would collapse this to a single
|
||||
command; that is the server admin's call, not ours.
|
||||
|
||||
`./deploy.sh` is the whole release: fetch, reset to `origin/<branch>`, install, migrate,
|
||||
seed content, collectstatic, restart, then it polls `/api/health` and prints the likely
|
||||
cause if the service does not come back.
|
||||
|
||||
**It repairs the server rather than stopping at it.** A hand-edited file used to make
|
||||
`git pull --ff-only` fail, which aborted the whole script — and because every later step
|
||||
was skipped, the server silently stayed on old code. Now local modifications (and local
|
||||
commits) are saved to a timestamped patch under `.deploy-backups/` and then discarded with
|
||||
`git reset --hard origin/<branch>`, and the deploy continues. Whatever state the server is
|
||||
left in, one run puts it back on exactly what Gitea has.
|
||||
|
||||
**`git clean` is never run there and must never be added.** `db.sqlite3`, `.env` and
|
||||
`.django_secret_key` are untracked and live in that directory; `reset --hard` leaves them
|
||||
alone, `clean -fd` would destroy the database, the config and every signed-in session.
|
||||
|
||||
Settings boot with working defaults (hosts, CSRF, CORS, secret key, cache), so a stale or
|
||||
partial `.env` on the server no longer breaks startup — env values are *merged with*
|
||||
those defaults, not replaced by them.
|
||||
|
||||
34
deploy.sh
34
deploy.sh
@@ -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 عوض شد، نسخهی تازه از اول اجرا میشود.
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user