deploy.sh: health check through the gunicorn socket, optional DEPLOY_BUNDLE when Gitea is unreachable
From the server the public URL goes through ArvanCloud and returned 504/000 (2026-10-07), so the final health step failed even for a healthy service. It now asks the gunicorn socket (path read from the unit) with our Host header first. DEPLOY_BUNDLE=<file> fetches from a git bundle instead of Gitea, only when set explicitly; without it an unreachable Gitea still stops the deploy. Tested: probe_health against a throwaway unix-socket server and a dead URL (bash -n clean). Not tested: the whole script end to end, until the next deploy. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
11
CLAUDE.md
11
CLAUDE.md
@@ -830,11 +830,12 @@ migrationها اعمال شدند، سهیار و `nextLessonId` از بیر
|
||||
بیبکاپ migrate نمیشود)؛ روی سرور هم اجرا شد (۲۰۲۶-۱۰-۰۷، `integrity_check: ok`). بیرون از ماشین بکاپی
|
||||
نیست.
|
||||
|
||||
**⚠️ دیپلو از ۲۰۲۶-۱۰-۰۷ بهخاطر Gitea:** از روی خودِ سرور `https://gitea.tadrisino2.ir` (حلشده به IPهای
|
||||
ArvanCloud) ۵۰۴ میدهد، پس `deploy.sh` در `git fetch` میایستد. آن روز کد با `git bundle` (از لپتاپ،
|
||||
`scp` به `.deploy-backups/`، `git fetch <bundle> main:refs/remotes/origin/main`، هشِ کامیت مقایسه شد،
|
||||
`git reset --hard origin/main`، `SKIP_PULL=1 ./deploy.sh`) رسید. علتِ ۵۰۴ پیدا نشد (مسیرِ آروان↔سرور؛ از لپتاپ ۲۰۰)؛
|
||||
مرحلهی «بررسیِ سلامت»ِ `deploy.sh` هم از داخلِ سرور همیشه شکست میخورد (همان مسیر). سلامت را از بیرون بسنجید. `sqlite3` روی سرور نصب نیست — برای بکاپِ دستی با `venv/bin/python` و
|
||||
**⚠️ Gitea از روی سرور:** از ۲۰۲۶-۱۰-۰۷ `https://gitea.tadrisino2.ir` از خودِ سرور (حلشده به IPهای
|
||||
ArvanCloud) ۵۰۴ میدهد و از لپتاپ ۲۰۰؛ علتش پیدا نشد (مسیرِ آروان↔سرور). `deploy.sh` در `git fetch` میایستد.
|
||||
راه: `DEPLOY_BUNDLE` (بخشِ «دیپلو» در `docs/operations.md`)، با مقایسهی هشِ کامیت. مرحلهی «بررسیِ سلامت»
|
||||
حالا از سوکتِ gunicorn میسنجد و از داخلِ سرور هم کار میکند.
|
||||
|
||||
`sqlite3` روی سرور نصب نیست — برای بکاپِ دستی با `venv/bin/python` و
|
||||
`sqlite3.Connection.backup()` بگیر، نه با `cp`.
|
||||
|
||||
بخشهای تاریخیِ زیر بهترتیبِ زمان نگه داشته شدهاند چون درسهایشان هنوز معتبر است؛
|
||||
|
||||
45
deploy.sh
45
deploy.sh
@@ -39,6 +39,21 @@ else
|
||||
)
|
||||
fi
|
||||
|
||||
# یک آدرسِ سلامت را میزند و CODE و BODY را میگذارد. آدرسِ `unix:/مسیر` یعنی مستقیم از سوکتِ
|
||||
# gunicorn (با هدرِ Host دامنهی خودمان): از داخلِ سرور آدرسِ عمومی از مسیرِ ArvanCloud برمیگردد و
|
||||
# ۲۰۲۶-۱۰-۰۷ ۵۰۴/۰۰۰ میداد، پس «سلامتِ» اسکریپت همیشه شکست میخورد حتی وقتی سرویس سالم بود.
|
||||
probe_health() {
|
||||
local target="$1" RESPONSE
|
||||
if [[ "$target" == unix:* ]]; then
|
||||
RESPONSE="$(curl -s -m 10 -w '\n%{http_code}' --unix-socket "${target#unix:}" \
|
||||
-H "Host: ${PUBLIC_HOST:-localhost}" http://localhost/api/health 2>/dev/null || printf '\n000')"
|
||||
else
|
||||
RESPONSE="$(curl -s -m 10 -w '\n%{http_code}' "$target" 2>/dev/null || printf '\n000')"
|
||||
fi
|
||||
CODE="${RESPONSE##*$'\n'}"
|
||||
BODY="${RESPONSE%$'\n'*}"
|
||||
}
|
||||
|
||||
# ─── ۱. کدِ تازه ────────────────────────────────────────────────────────────
|
||||
if [ "${SKIP_PULL:-0}" != "1" ]; then
|
||||
step "گرفتنِ کدِ تازه"
|
||||
@@ -49,8 +64,21 @@ if [ "${SKIP_PULL:-0}" != "1" ]; then
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo main)"
|
||||
[ "$BRANCH" = "HEAD" ] && BRANCH=main
|
||||
|
||||
# DEPLOY_BUNDLE=/مسیر/فایل.bundle: کد را از یک git bundle میگیرد، نه از gitea. فقط وقتی صریحاً
|
||||
# داده شود؛ برای روزی که سرور به gitea نمیرسد (۲۰۲۶-۱۰-۰۷: از سرور ۵۰۴ از مسیرِ ArvanCloud، از بیرون
|
||||
# ۲۰۰). bundle را از جایی بسازید که به origin/<branch> دسترسی دارد و هشِ کامیتِ چاپشده را با آنجا
|
||||
# مقایسه کنید. بدونِ این متغیر هیچ جایگزینی نیست و نرسیدن به gitea همچنان دیپلو را میایستاند.
|
||||
if [ -n "${DEPLOY_BUNDLE:-}" ]; then
|
||||
[ -f "$DEPLOY_BUNDLE" ] || fail "DEPLOY_BUNDLE=$DEPLOY_BUNDLE وجود ندارد."
|
||||
warn "کد از bundle گرفته میشود، نه از gitea: $DEPLOY_BUNDLE"
|
||||
git bundle verify "$DEPLOY_BUNDLE" >/dev/null 2>&1 || fail "bundle معتبر نیست یا کامیتِ پایهاش روی این سرور نیست."
|
||||
git fetch "$DEPLOY_BUNDLE" "$BRANCH:refs/remotes/origin/$BRANCH" \
|
||||
|| fail "گرفتن از bundle نشد."
|
||||
else
|
||||
git fetch origin "$BRANCH" \
|
||||
|| fail "به gitea وصل نشد. شبکه یا دسترسیِ مخزن را بررسی کنید."
|
||||
|| fail "به gitea وصل نشد. شبکه یا دسترسیِ مخزن را بررسی کنید (اگر فقط از این سرور نمیرسد: DEPLOY_BUNDLE، بالای همین فایل)."
|
||||
fi
|
||||
printf ' origin/%s = %s\n' "$BRANCH" "$(git rev-parse --short "origin/$BRANCH")"
|
||||
|
||||
# اگر کسی روی سرور فایلی را دستی عوض کرده، اول نسخهی پشتیبان، بعد کنار
|
||||
# گذاشتنش.
|
||||
@@ -350,26 +378,31 @@ fi
|
||||
|
||||
# ─── ۸. آیا واقعاً بالا آمد؟ ───────────────────────────────────────────────
|
||||
step "بررسیِ سلامت"
|
||||
PUBLIC_URL=""
|
||||
if [ -z "${TADRISINO_HEALTH_URL:-}" ]; then
|
||||
PUBLIC_URL="$(python -c 'import django, os; os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings"); django.setup(); from django.conf import settings; print(settings.PUBLIC_BASE_URL)' 2>/dev/null | tail -n 1)"
|
||||
if [ -n "$PUBLIC_URL" ]; then
|
||||
PUBLIC_HOST="${PUBLIC_URL#*://}"; PUBLIC_HOST="${PUBLIC_HOST%%/*}"
|
||||
# سوکتِ gunicorn را از خودِ یونیت میخوانیم تا مسیرش در این فایل تکرار نشود.
|
||||
SOCK=""
|
||||
if [ -n "${SERVICE:-}" ] && command -v systemctl >/dev/null 2>&1; then
|
||||
SOCK="$(systemctl cat "${SERVICE}.service" 2>/dev/null | grep -oE 'unix:[^ "]+' | head -n 1 || true)"
|
||||
fi
|
||||
[ -n "$SOCK" ] && [ -S "${SOCK#unix:}" ] && HEALTH_URLS=("$SOCK" "${HEALTH_URLS[@]}")
|
||||
HEALTH_URLS+=("$PUBLIC_URL/api/health" "$PUBLIC_URL/api/health/")
|
||||
fi
|
||||
fi
|
||||
CODE=000; BODY=""
|
||||
for attempt in 1 2 3 4 5 6 7 8 9 10; do
|
||||
for url in "${HEALTH_URLS[@]}"; do
|
||||
# یک درخواست، نه دو تا: دو فراخوانیِ جدا میتوانست دو وضعیتِ متفاوت ببیند.
|
||||
RESPONSE="$(curl -s -m 10 -w '\n%{http_code}' "$url" 2>/dev/null || printf '\n000')"
|
||||
CODE="${RESPONSE##*$'\n'}"
|
||||
BODY="${RESPONSE%$'\n'*}"
|
||||
probe_health "$url"
|
||||
if [ "$CODE" = "200" ]; then
|
||||
printf '%s✓ سرویس سالم بالا آمد%s (%s)\n' "$C_OK" "$C_OFF" "$url"
|
||||
printf '%s\n' "$BODY" | python -m json.tool 2>/dev/null | sed 's/^/ /' || printf ' %s\n' "$BODY"
|
||||
# ۲۰۰ گرفتن بهتنهایی یعنی «چیزی جواب میدهد»، نه «کدِ جدید بالاست».
|
||||
if [ "$RESTART_OK" != "1" ]; then
|
||||
warn "ولی سرویس ریاستارت نشد — این جوابْ از پروسهی قدیمی است و کدِ
|
||||
جدید هنوز سرو نمیشود. تا ریاستارت نکنید دیپلوی ناتمام است."
|
||||
جدید هنوز سرو نمیشود. تا ریاستارت نکنید دیپلو ناتمام است."
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
|
||||
@@ -34,7 +34,15 @@ poll کردنِ `/api/health`.
|
||||
> `systemctl show zareian-mathapp -p ActiveEnterTimestamp`
|
||||
|
||||
متغیرهای اختیاری: `SKIP_PULL=1`، `TADRISINO_SERVICE`، `TADRISINO_HEALTH_URL`،
|
||||
`PIP_INDEX_URL` (وقتی pypi.org تایماوت میدهد).
|
||||
`PIP_INDEX_URL` (وقتی pypi.org تایماوت میدهد)، `DEPLOY_BUNDLE`.
|
||||
|
||||
- **سلامت از سوکتِ gunicorn** (مسیرش را از یونیت میخواند) با هدرِ `Host` دامنهی خودمان سنجیده
|
||||
میشود، چون از داخلِ سرور آدرسِ عمومی از مسیرِ ArvanCloud برمیگردد و ۵۰۴/۰۰۰ میداد (۲۰۲۶-۱۰-۰۷).
|
||||
- **اگر سرور به Gitea نمیرسد** (از سرور ۵۰۴، از لپتاپ ۲۰۰): روی لپتاپ
|
||||
`git bundle create x.bundle <هشِ origin/main روی سرور>..main`، `scp` به
|
||||
`~/mathapp-v2/.deploy-backups/`، و روی سرور
|
||||
`DEPLOY_BUNDLE=.deploy-backups/x.bundle ./deploy.sh`. هشِ `origin/main` که چاپ میشود را با لپتاپ
|
||||
مقایسه کنید. بدونِ این متغیر جایگزینی نیست و نرسیدن به Gitea دیپلو را میایستاند.
|
||||
|
||||
## بررسیِ سلامت
|
||||
|
||||
|
||||
Reference in New Issue
Block a user