Files
2026-10-10 14:21:04 +03:30

139 lines
5.6 KiB
Bash

#!/usr/bin/env bash
#
# توقف همهٔ سرویس‌های محلی کتیبه (web / worker / scanner) که با ./run.sh بالا آمده‌اند.
#
# استفاده:
# ./stop.sh توقف همه + گزارش
# ./stop.sh --quiet بدون خروجی (برای صدا زدن از run.sh)
#
# ترتیب کار: اول PIDهای ثبت‌شده در .run/ (با SIGTERM و مهلت، سپس SIGKILL)،
# بعد پاک‌سازی هر پروسهٔ جامانده با الگوی manage.py — تا اگر pidfile گم/کهنه شده بود
# هم چیزی روی پورت ۸۰۰۰ نماند.
set -uo pipefail
cd "$(dirname "$(readlink -f "$0")")"
RUN_DIR=".run"
WEB_PORT="8000"
QUIET=0
[ "${1:-}" = "--quiet" ] && QUIET=1
say() { [ "$QUIET" = "1" ] || echo "$@"; }
# الگوی پروسه‌های همین پروژه. عمداً فقط روی manage.py و زیرفرمان‌هایش قفل می‌شود:
# cmdline این اسکریپت («bash ./stop.sh») با آن مطابقت نمی‌کند، پس خودش را نمی‌کشد.
PATTERN='manage\.py (runserver|sms_worker|sms_scan)'
# پاک‌سازی جان‌مانده‌های سمت ویندوز.
#
# چرا لازم است: از WSL، فایل ``.venv/Scripts/python.exe`` یک باینری ویندوزی است و از طریق
# WSL interop اجرا می‌شود. اگر والدِ interop از بین برود، پروسهٔ ویندوزی یتیم می‌شود و دیگر
# در ``ps`` داخل WSL دیده نمی‌شود، ولی پورت ۸۰۰۰ را همچنان گرفته نگه می‌دارد (نتیجه: سرور
# بعدی bind نمی‌شود یا نسخهٔ قدیمی کد سرو می‌شود). پس صریحاً با ابزار ویندوز پاک می‌شوند.
#
# فیلتر محافظتی: فقط پروسه‌های ``python`` که ``manage.py`` اجرا می‌کنند و *یا* از مسیر همین
# پروژه/venv آمده‌اند *یا* پورت ۸۰۰۰ را اشغال کرده‌اند. هیچ پروژهٔ دیگری آسیب نمی‌بیند.
windows_cleanup() {
command -v powershell.exe >/dev/null 2>&1 || return 0
command -v taskkill.exe >/dev/null 2>&1 || return 0
local pids
pids="$(powershell.exe -NoProfile -Command "
\$port = $WEB_PORT
\$listen = @()
try { \$listen = @((Get-NetTCPConnection -LocalPort \$port -State Listen -ErrorAction Stop).OwningProcess) } catch { \$listen = @() }
Get-CimInstance Win32_Process -Filter \"Name='python.exe' or Name='pythonw.exe'\" |
Where-Object {
\$_.CommandLine -match 'manage\.py' -and (
\$_.ExecutablePath -like '*khatibi*' -or
\$_.CommandLine -like '*khatibi*' -or
(\$listen -contains \$_.ProcessId)
)
} | ForEach-Object { \$_.ProcessId }
" 2>/dev/null | tr -d '\r' | grep -E '^[0-9]+$' || true)"
[ -n "$pids" ] || return 0
local p
for p in $pids; do
taskkill.exe /F /PID "$p" >/dev/null 2>&1 || true
done
say " پروسه‌های ویندوزی پاک شدند: $(echo "$pids" | tr '\n' ' ')"
}
is_our_process() {
local pid="$1" cmd
[ -r "/proc/$pid/cmdline" ] || return 1
cmd="$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true)"
case "$cmd" in *manage.py*) return 0 ;; *) return 1 ;; esac
}
stop_pid() {
local pid="$1" name="$2"
[ -n "$pid" ] || return 0
kill -0 "$pid" 2>/dev/null || return 0
# محافظت در برابر PID کهنه: اگر این PID دیگر مال پروژه نیست، دست نزن.
if ! is_our_process "$pid"; then
say " $name: pid $pid دیگر مربوط به این پروژه نیست؛ رد شد"
return 0
fi
kill -TERM "$pid" 2>/dev/null || true
for _ in $(seq 1 20); do
kill -0 "$pid" 2>/dev/null || { say " $name (pid $pid) متوقف شد"; return 0; }
sleep 0.25
done
kill -KILL "$pid" 2>/dev/null || true
say " $name (pid $pid) پس از مهلت مجبور به توقف شد"
}
say "==> توقف سرویس‌های محلی"
stopped_any=0
if [ -d "$RUN_DIR" ]; then
shopt -s nullglob
for pidfile in "$RUN_DIR"/*.pid; do
name="$(basename "$pidfile" .pid)"
pid="$(cat "$pidfile" 2>/dev/null || true)"
[ -n "$pid" ] && stopped_any=1
stop_pid "$pid" "$name"
rm -f "$pidfile"
done
shopt -u nullglob
fi
# پاک‌سازی باقی‌مانده‌ها (pidfile گم‌شده، اجرای دستی، کرش قبلی)
leftover=0
if command -v pkill >/dev/null 2>&1; then
if pgrep -f "$PATTERN" >/dev/null 2>&1; then
leftover=1
pkill -TERM -f "$PATTERN" 2>/dev/null || true
for _ in $(seq 1 20); do
pgrep -f "$PATTERN" >/dev/null 2>&1 || break
sleep 0.25
done
pkill -KILL -f "$PATTERN" 2>/dev/null || true
say " پروسه‌های جامانده پاک شدند"
fi
fi
if [ "$stopped_any" = "0" ] && [ "$leftover" = "0" ]; then
say " چیزی برای متوقف‌کردن نبود."
fi
windows_cleanup
# تأیید نهایی: پورت ۸۰۰۰ آزاد باشد.
# --noproxy اجباری است: در این محیط یک پروکسی محلی (127.0.0.1:10808) هست که درخواست به
# localhost را می‌گیرد و 503 برمی‌گرداند؛ بدون دور زدنش این بررسی false positive می‌دهد.
if command -v curl >/dev/null 2>&1; then
code="$(curl -s --noproxy '*' -o /dev/null -w '%{http_code}' --max-time 3 \
"http://127.0.0.1:$WEB_PORT/" 2>/dev/null || true)"
if [ -n "$code" ] && [ "$code" != "000" ]; then
say " هشدار: هنوز چیزی روی پورت $WEB_PORT پاسخ می‌دهد (HTTP $code) — سروری خارج از این اسکریپت."
fi
fi
say "✅ پایان."