Skip to content

FA Design Notes

Arian Omrani edited this page Oct 11, 2026 · 5 revisions

یادداشت‌های طراحی

زبان: English · فارسی

pulsecheck چطور ساخته شده، چرا به این شکل، و در راه اجرا شدن روی پنج محیط مختلف چه چیزهایی دستمان آمد. اگر می‌خواهید در پروژه مشارکت کنید یا مشکلی را ردیابی کنید به کارتان می‌آید.

نقشه‌ی کد (src/pulsecheck/)

ماژول کار کجا استفاده می‌شود
config.py dataclass برای هر کلید، پر کردن ${ENV}، بررسی نوع‌ها و قالب پیام‌ها همه‌جا
result.py Result یک بررسی، و limit() برای max_response_ms همه‌جا
state.py ماشین حالت up/down (step)، Store روی SQLite و BlobStore روی یک سند JSON CLI و سرورلس
engine.py یک اجرا: انتخاب سایت‌های سررسیده، بررسی هم‌زمان، تصمیم برای هشدار، ذخیره‌ی وضعیت و ارسال تلمتری؛ و status() همه‌جا
otel.py ساخت بار OTLP/HTTP JSON و ارسال آن همه‌جا
httpnotify.py تبدیل آدرس‌های هم‌قالب shoutrrr به درخواست HTTPS ساده برای ۱۲ سرویس Vercel و Cloudflare
checks.py HTTP (httpx)، TCP، ping و انقضای TLS CLI و Vercel
notify.py اجرای shoutrrr (image داکر یا فایل اجرایی)، تأیید آدرس، دانلود فایل اجرایی (با SHA-256) CLI و Vercel
setup.py، cli.py دستورها، پیدا کردن فایل تنظیم، .env، init، بلوک cron و update CLI
web.py و app.py (ریشه‌ی مخزن) Vercel: برنامه‌ی WSGI و وضعیت در Upstash Redis Vercel
deploy/cloudflare/ build.py (Build Output)، src/entry.py (Worker) و deploy.sh Cloudflare

ایده‌ی اصلی: ورودی و خروجی را از بیرون می‌دهیم

engine.run(cfg, store, check, send, post=...) خودش به شبکه، دیسک یا ساعتی که نشود جایش را عوض کرد دست نمی‌زند. هر چه لازم دارد را فراخواننده می‌دهد:

  • check(site) که یک Result برمی‌گرداند (در CLI: thread با httpx، socket و ping؛ در Cloudflare: fetch)،
  • send(urls, text) (در CLI: shoutrrr؛ در سرورلس: ارسال‌کننده‌های HTTP)،
  • post(url, headers, body) برای OpenTelemetry،
  • یک store (فایل SQLite، یا یک سند JSON در Redis یا Workers KV).

برای همین یک موتور از cron، GitHub Actions، GitLab CI، Vercel و Cloudflare Worker اجرا می‌شود و بیشتر رفتارها را می‌شود بدون شبکه تست کرد.

وضعیت

  • SQLite (Store): هر بررسی یک ردیف است (۴۰۰ روز نگه داشته می‌شود) و وضعیت فعلی هر سایت هم جدا ذخیره می‌شود. p95 دقیق می‌دهد.
  • سند JSON (BlobStore): وضعیت فعلی به‌اضافه‌ی جمع روزانه‌ی هر سایت (تعداد بررسی، تعداد موفق، جمع تأخیر) برای ۹۰ روز. بنابراین در هر اجرا فقط یک سند کوچک خوانده و نوشته می‌شود. p95 ندارد. اگر دو اجرا روی هم بیفتند ممکن است یک به‌روزرسانی از دست برود (آخرین نوشتن می‌ماند)، ولی با فاصله‌ی cron این تقریباً پیش نمی‌آید.
  • وضعیت هر سایت این‌ها را دارد: status، تعداد شکست‌های پشت‌سرهم fails، since، زمان آخرین بررسی last و یک meta کوچک (زمان آخرین هشدار و آخرین هشدار گواهی، برای یادآوری‌ها).

تصمیم‌هایی که دانستنشان می‌ارزد

  • هشدار فقط هنگام تغییر. step() تابعی خالص با تست است. یادآوری و هشدار گواهی در موتور روی آن سوار شده‌اند.
  • dataclass به‌جای pydantic. Worker کلودفلر باید بدون هیچ بسته‌ی شخص ثالثی اجرا شود. نمایه‌ی بسته‌های Pyodide از بعضی شبکه‌ها در دسترس نبود و بسته‌ی کمتر یعنی cold start سریع‌تر. config.build() نوع‌ها را بررسی می‌کند و خطاهایی مثل config.sites[0].timeout: expected a number می‌دهد.
  • YAML فقط در لبه‌ها. Worker هنگام build یک JSON می‌گیرد و ruamel.yaml فقط در لحظه‌ی نیاز import می‌شود.
  • یک shoutrrr، دو راه. CLI از shoutrrr واقعی استفاده می‌کند. جایی که اجرا نمی‌شود (Cloudflare) یا شاید وجود نداشته باشد (Vercel)، ارسال‌کننده‌های HTTP داخلی سرویس‌های رایج را پوشش می‌دهند. Vercel برای بقیه، فایل اجرایی واقعی را در /tmp دانلود می‌کند و SHA-256 آن را با checksums.txt همان release مقایسه می‌کند.
  • رمزها در خروجی نمی‌آیند. کانال‌ها به شکل telegram://*** چاپ می‌شوند. endpointهای Vercel و Cloudflare توکن را با hmac.compare_digest مقایسه می‌کنند. فایل .env با دسترسی ۶۰۰ ساخته می‌شود و اگر CRON_SECRET تنظیم نشده باشد، endpoint مربوط به cron در Vercel هر درخواستی را رد می‌کند.
  • init را می‌شود چندبار اجرا کرد. تنظیمات موجود را هرگز بازنویسی نمی‌کند و cron job داخل یک بلوک علامت‌دار است که جایگزین می‌شود و تکرار نمی‌شود.

آنچه با deploy واقعی یاد گرفتیم

پلتفرم یافته نتیجه
CLI به اسم cf در کلودفلر بتا است و بسته‌ی dev server پایتونش منتشر نشده، پس نمی‌تواند Python Worker بسازد Build Output را خودمان می‌نویسیم و cf deploy --prebuilt را اجرا می‌کنیم
Python Worker فقط ماژول اصلی import می‌شود (بقیه‌ی ماژول‌ها و فایل‌های داده mount نمی‌شوند) build.py پکیج را در یک entry.py می‌گذارد و ماژول‌های pulsecheck.* را هنگام import ثبت می‌کند
Python Worker در scheduled(self, controller, env, ctx) مقدار env برابر None است از self.env استفاده کنید
Python Worker خط CONFIG_TEXT = None در سطح ماژول، مقدار تعبیه‌شده را بی‌صدا پاک کرد entry از globals().get(...) استفاده می‌کند و یک تست، bundle ساخته‌شده را اجرا می‌کند
ntfy.sh روی Cloudflare به IP خروجیِ مشترک Worker کد 429 داد خطای هشدار لاگ می‌شود و اجرا را متوقف نمی‌کند
build در Vercel .python-version با مقدار 3.15 خطا می‌دهد (No interpreter found) build.env.UV_PYTHON=3.14 در vercel.json
اجرا در Vercel توابع روی پایتون ۳٫۱۲٫۱۴ اجرا شدند؛ ping و Docker نبود، /tmp قابل نوشتن بود، HTTPS خروجی و اجرای فایل اجرایی دانلودشده کار کرد shoutrrr در صورت نیاز دانلود می‌شود
deploy در Vercel یک فایل ۲۰ مگابایتی در آپلود باعث شد deploy گیر کند هیچ فایل اجرایی‌ای آپلود نمی‌کنیم؛ هنگام اجرا دانلود می‌شود
Upstash در Vercel صاحب حساب باید یک‌بار شرایط Upstash را در مرورگر بپذیرد؛ بعد از آن vercel integration add upstash/upstash-kv پایگاه Redis را می‌سازد و KV_REST_API_URL و KV_REST_API_TOKEN را تزریق می‌کند مستند شد و با پایگاه داده‌ی واقعی تست شد
دام‌های تست pkill -f خط فرمان خودش را هم می‌گرفت و شل را می‌کشت؛ __init__.py خالی در بعضی آپلودها حذف می‌شود؛ ping به 192.0.2.1 پشت بعضی شبکه‌ها موفق می‌شود در تست‌ها و اسکریپت‌ها کنار گذاشته شدند

چه چیزی تست شده و چه چیزی نه

  • خودکار (۲۸ تست، CI روی پایتون ۳٫۱۲ تا ۳٫۱۵): ماشین حالت؛ جریان‌های موتور (down، up، degraded، یادآوری، گواهی، maintenance، برچسب، interval)؛ قاعده‌های تنظیمات؛ ارسال‌کننده‌های HTTP؛ بار OpenTelemetry؛ .env؛ بلوک cron؛ init؛ انتخاب دستور update؛ checksum دانلود shoutrrr؛ handler مربوط به Vercel با Redis ساختگی؛ Worker و bundle ساخته‌شده‌ی Worker با ماژول‌های ساختگی محیط اجرا.
  • دستی روی سرویس‌های واقعی: خط‌لوله‌ی انتشار (نسخه، تگ، GitHub release، آپلود در PyPI و uv tool install pulsecheckcli)، یک Worker کلودفلر (deploy، اجرای cron، وضعیت در KV، رسیدن وب‌هوک، لاگ‌ها) و یک تابع Vercel با Upstash Redis (deploy، احراز هویت، خواندن تنظیمات، ماندن وضعیت بین اجراها، /api/status و اجرای shoutrrr).
  • با سرویس واقعی تست نشده: endpointهای /status و /run در Worker؛ crontab روی یک سیستم واقعی (یک نمونه‌ی ساختگی به کار رفت)؛ رسیدن پیام تلگرام، اسلک و … از ارسال‌کننده‌های HTTP داخلی (فقط شکل بار و shoutrrr verify آدرس‌ها بررسی شد).

فرایند انتشار

پروژه‌ی PyPI اسمش pulsecheckcli است و دستورها pulsecheck و اسم کوتاه pck هستند. انتشار خودکار است و از Conventional Commits تبعیت می‌کند (feat نسخه‌ی minor می‌سازد، fix و perf نسخه‌ی patch). وقتی CI روی main سبز شود، publish.yml اجرا می‌شود و python-semantic-release مقدار version در pyproject.toml را بالا می‌برد، CHANGELOG.md را به‌روز می‌کند، تگ vX.Y.Z را می‌زند و GitHub release را می‌سازد. بعد با Trusted Publisher (environment به اسم pypi، بدون توکن) بسته را build و در PyPI منتشر می‌کند. هیچ مرحله‌ی دستی‌ای لازم نیست؛ با Run workflow هم می‌شود سطح افزایش نسخه را اجباری کرد. نسخه‌ی 0.1.0 در ۱۱ اکتبر ۲۰۲۶ همین‌طور منتشر شد. فایل CONTRIBUTING.md در مخزن را ببینید.

Clone this wiki locally