در بقیهی پروژهها — پایشگر مشاغل NHS، API تشخیص پورت، سایتهای مشتریان — مدام به همان چیز نیاز داشتم: بیلدش کن، تستش کن، دیپلویش کن. GitHub Actions انتخاب پیشفرض بود، و لایهی رایگانش، ۲۰۰۰ دقیقه در ماه حتی با نگاه سخاوتمندانه، بهنظر زیاد میرسید.
چالش: متوقف کردن سوختن دقیقههای رایگان CI گیتهاب — بهخصوص روی بیلدهای Electron که کند هستند — بدون پرداخت بهازای دقیقه برای محاسباتی که بیشترش از قبل بلااستفاده داشتم.
چرا فقط یک بیلد Electron میتواند یک لایهی رایگان را بخورد
بستهبندی یک اپلیکیشن دسکتاپ با electron-builder روی یک رانر میزبانیشده کار سریعی نیست: نصب وابستگیها از صفر، کامپایل ماژولهای native، تولید نصبکنندهی جداگانه برای هر سیستمعامل — ویندوز و لینوکس، دستکم. این را برای یک اپلیکیشن انجام بده، بعد لینتینگ، تستها و مراحل دیپلوی که هر پروژهی دیگر هم لازم داشت را اضافه کن، و دقیقهها خیلی زودتر از ماه ناپدید میشدند.
سؤال واقعی: اصلاً چرا برای یک رانر پول بدهم؟
هیچکدام از آن مراحل واقعاً به یک ماشین ابری موقت نیاز نداشتند — آنها به یک ماشین نیاز داشتند، و من از قبل سرورها و کامپیوترهایی داشتم که بیشترشان بلااستفاده بودند. چیزی که کم بود محاسبات نبود. چیزی بود که منتظر یک محرک بماند، به یک سرور وصل شود، و مراحل را به ترتیب اجرا کند. این مسئلهی بسیار کوچکتری از «پلتفرم CI/CD» است، و همان چیزی است که CID واقعاً حل میکند.
این هم هیچوقت فقط دربارهی پول ندادن به گیتهاب نبود. اجرای آن توسط خودم یعنی لاگهای اجرا و رازهای پشت یک دیپلوی روی زیرساختی میماندند که واقعاً کنترلش میکردم، بهجای زندگی داخل پلتفرم شخص دیگری — و این فضا را برای شکل دادن به دسترسی بهروشی که یک تیم واقعی نیاز دارد باز گذاشت، بهجای رضایت دادن به هرچه یک محصول CI شخصثالث اتفاقی ارائه میداد.
تصمیمگیری دربارهی اینکه CID چه کاری بکند و نکند
یک سرویس میزبانیشده که ریپوها را کلون میکند و بیلدها را برایتان اجرا میکند، باید آنلاین بماند، کلیدهای SSH همه را نگه دارد، و یکجوری پول آن پرداخت شود — دقیقاً همان هزینهای که میخواستم حذف کنم. پس اجرای واقعی بهجایش به یک اپلیکیشن دسکتاپ روی ماشین خودم منتقل شد: CID-app، ساختهشده با Electron و React. مستقیماً ریپوی گیت محلی را میخواند (از طریق simple-git)، به سرورهای مقصد از طریق SSH/SFTP وصل میشود (ssh2، با پشتیبانی پروکسی SOCKS5 برای هرچیز پشت یک bastion host)، و مراحل هر ورکفلو را آنجا اجرا میکند.

CID-api — با Express و MongoDB — فقط پروژهها، تعاریف ورکفلو، رازها و کلیدهای رمزنگاریشده، و تاریخچهی اجرا را ذخیره میکند. هرگز به کد یا سرورهای شما دست نمیزند. همین تفکیک است که کل ماجرا را رایگان نگه میدارد: API کوچک و ارزان برای میزبانی است، و محاسباتی که قبلاً دقیقههای GitHub Actions را میخورد، حالا روی سختافزاری اتفاق میافتد که از قبل مال من بود.

محرکها بدون سروری که آنها را دریافت کند
نبود سرور رو-به-عموم یعنی وبهوکی برای دریافت نیست. محرک push با این کار میکند که CID-app پوشهی گیت محلی را تقریباً هر ۱۵ ثانیه پول میکند و آخرین کامیت را با آخرین چیزی که دیده مقایسه میکند. محرک cron خود node-cron است که داخل پروسهی اپلیکیشن اجرا میشود، بدون هیچ سرویس زمانبند جداگانهای. هردو واکنش آنی را با چیزی رایگان و ساده برای اجرا معامله میکنند.
این یک محدودیت واقعی است، نه یک غفلت: یک زمانبندی فقط وقتی فعال میشود که CID-app روی یک ماشینی باز باشد. همان شکل مسئلهای است که پایشگر مشاغل NHS با نیاز به یک تب مرورگر باز به آن برخورد — اینجا متفاوت حل شده، اما حذف نشده. این هزینهی خود این پروژه برای رایگان ماندن است.
مدیریت کلیدهای SSH و رازهای دیگران
بهمحض اینکه این دیگر فقط پایپلاین خودم نبود — CID بهعنوان یک محصول واقعی با ثبتنام خودش منتشر شد — ناگهان سرورها و رازهای افراد دیگر هم درگیر شدند، و استاندارد اعتماد کاملاً تغییر کرد. کلیدهای خصوصی SSH و رازهای پروژه در حالت سکون رمزنگاری میشوند (رازها با AES-256-GCM)، فقط برای همان لحظهای که یک اتصال واقعاً باز میشود رمزگشایی میشوند، و API هرگز در موقعیتی نیست که کد شما را ببیند. یک گزینهی خوداستقرار CID-api هم برای هرکسی که حتی به کانفیگ سرویس میزبانیشده اعتماد نمیکند وجود دارد.
یک اجرا واقعاً چه شکلی است
یک اجرا با یک فاز همگامسازی فایلها روی SFTP شروع میشود، که فایلهای تغییریافته را مستقیماً به مسیر دیپلوی سرور آپلود میکند — بدون هیچ نیازی به دسترسی گیت روی سرور مقصد، بدون کلید دیپلوی برای مدیریت آنجا. بعد فرمان شل هر مرحله از همان مسیر روی SSH اجرا میشود، به ترتیب، با متغیرهای محیطی و رازهای پروژه که ابتدا export میشوند. لاگها همانطور که خروجی SSH میرسد، زنده به پنل اجرا استریم میشوند؛ اگر یک مرحله شکست بخورد، اجرا متوقف میشود و همهچیز بعد از آن رد شده علامتگذاری میشود.

نتیجهی نهایی
CID حالا CI/CD بقیهی پروژههای pumpkins.ir را اجرا میکند — از جمله بیلدهای Electron برای خود CID-app، روی سختافزار واقعی بهجای یک رانر اندازهگیریشده. بهجای ماندن بهعنوان یک ابزار داخلی، بهعنوان محصول خودش منتشر شد، توزیعشده بهصورت یک نصبکنندهی ویندوز از طریق CID-releases، با گزینهی خوداستقرار API که همچنان روی میز برای هرکسی که بخواهد باقی مانده.
چه چیزی از این تجربه یاد گرفتم
- «دقیقههای رایگان CI» واقعاً فقط ماشین شخص دیگری است که بهازای دقیقه اجاره شده — اگر از قبل یک ماشین دارید، راهحل ارزانتر ارکستراسیون است، نه دنبال کردن یک لایهی رایگان بزرگتر.
- نداشتن یک سرور عمومی برای دریافت رویدادها یک محدودیت واقعی است، نه یک ناهار مجانی: پولینگ بهجای وبهوک، کرون درونپروسهای بهجای یک سرویس زمانبند. این معامله را صراحتاً بپذیرید، نه اینکه وانمود کنید وجود ندارد.
- همان لحظهای که یک ابزار شروع به نگهداشتن اطلاعات ورود شخص دیگری میکند، امنیت «روی ماشین من کار میکند» دیگر کافی نیست — در حالت سکون رمزنگاری کنید، فقط در لحظهی استفاده رمزگشایی کنید، و برای مردم یک راه خروج خوداستقرار بگذارید.