#!/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 "✅ پایان."