diff --git a/.gitignore b/.gitignore index eeba69c..e917508 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ data/textbooks/* # خروجیِ collectstatic — تولیدی است، در هر دیپلوی از نو ساخته می‌شود. staticfiles/ + +# نسخه‌ی پشتیبانِ تغییرهای دستیِ روی سرور که deploy.sh کنار می‌گذارد. +.deploy-backups/ diff --git a/CLAUDE.md b/CLAUDE.md index b849e93..e247708 100644 --- a/CLAUDE.md +++ b/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/`, 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/`, 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. diff --git a/deploy.sh b/deploy.sh index 6a4d2e9..4a3262d 100755 --- a/deploy.sh +++ b/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 عوض شد، نسخه‌ی تازه از اول اجرا می‌شود. #