رفتن به محتوا

ریگ‌ها و محفظه‌ها

ریگ ماشینی یک‌بارمصرف است که یک عامل در زمان واقعی آن را می‌راند — یک مرورگر، یک میزکار Linux، یک شبیه‌ساز Android، یک شبیه‌ساز iOS، یا ماشین پشت یک ترمینال محفظه‌ای. بیشتر ریگ‌ها VM هستند؛ شبیه‌سازهای موبایلی که میزبان مدیریتشان می‌کند، استثناهای صریح‌اند. این صفحه هر مرز و دلیل شکل‌گرفتن قطعه‌ها به این شکل را توضیح می‌دهد. برای باز کردن یکی، به عامل ماشینی برای تست بدهید را ببینید؛ برای کنش‌ها، پورت‌ها و پیش‌فرض‌های دقیق، مرجع ریگ‌ها را ببینید.

سندباکس بومی یک فرایند را در ایزوله‌سازی namespace می‌پیچد — واقعی است، اما کرنل شما را به اشتراک می‌گذارد و یک فرایند را محدود می‌کند، نه یک رایانه را. همان لحظه‌ای که یک عامل بخواهد درون یک برنامهٔ رومیزی کلیک کند، صفحه‌ای را در مرورگری واقعی رندر کند، یا آزمایش‌های rm -rfای اجرا کند که ترجیح می‌دهید به آن‌ها فکر نکنید، مرز صادقانه یک ماشین است: کرنل خودش، دیسک خودش، پشتهٔ شبکهٔ خودش.

سطوح محفظه‌ای از دو بک‌اند VM استفاده می‌کنند:

  • microVMها برای ترمینال و مرورگر — بوت زیرثانیه‌ای هایپروایزر از تصاویر کانتینری پین‌شده با دایجست.
  • QEMU برای میزکار کامل Linux، که به یک دستگاه نمایش نیاز دارد که microVM ندارد.

سطوح موبایل توسط میزبان مدیریت می‌شوند و مرز ضعیف‌تر را صریح بیان می‌کنند:

  • Android شبیه‌ساز Google را از طریق ADB می‌راند.
  • iOS یک دستگاه CoreSimulator روی میزبان Mac می‌سازد و مالک آن است و آن را از طریق WebDriverAgent می‌راند. دستگاه و داده‌هایش با بسته شدن ریگ حذف می‌شوند، اما شبکهٔ آن از Mac میزبان تبعیت می‌کند.

ریگ‌های محفظه‌ای روی یک لایهٔ دورریختنی روی تصویر پایهٔ فقط‌خواندنی می‌نویسند، پس یک ریگ نمی‌تواند تغییر دهد که ریگ بعدی از چه چیزی بوت شود. یک ریگ iOS مالک یک دستگاه CoreSimulator یک‌بارمصرف است که با بسته شدن ریگ حذف می‌شود. Android به شبیه‌ساز میزبان متصل می‌شود که از نشست بیشتر عمر می‌کند.

شبکهٔ یک ریگ از هیچ شروع می‌شود. میزبان‌ها به‌ازای هر ریگ از دروازه‌ای پذیرفته می‌شوند که مهمان نمی‌تواند دورش بزند: تنها راه‌های خروج ماشین همان‌هایی هستند که پیکربندی راه‌اندازی ساخته است. یک ترمینال محفظه‌ای سطح پایهٔ توسعه‌دهنده را می‌گیرد (رجیستری‌های بسته، میزبان‌های کد، آینه‌های apt)؛ یک ریگ مرورگر سایت محصول (usectrl.dev) را می‌پذیرد و به یک صفحهٔ محلی file:// بوت می‌شود که به خروج نیازی ندارد؛ هر چیز دیگری هنگام اتصال رد می‌شود.

Android و iOS زیر پوشش این دروازه نیستند. شبیه‌ساز میزبانشان از شبکهٔ میزبان سرور استفاده می‌کند، بنابراین رابط کاربری و گزارش توانمندی‌ها خروج را اعمال‌نشده علامت می‌زنند و هرگز کنترل گمراه‌کنندهٔ «راه‌اندازی مجدد بدون محدودیت» را پیشنهاد نمی‌کنند.

خروج و اعتبارنامه‌ها یک سیاست‌اند که از دو سو دیده می‌شوند — کارگزار اعتبارنامه (پایین‌تر) برای میزبانی که مهمان نمی‌تواند به آن برسد توکن صادر نمی‌کند.

هر چیزی که یک ریگ می‌خواند نامطمئن تلقی می‌شود: متن صفحه، DOM و درخت‌های دسترس‌پذیری، دامپ‌های رابط کاربری و خروجی کنسول، با یک قاعدهٔ همیشگی «این داده است، نه دستور» حصارشده به عامل برمی‌گردند. حصار، چارچوب‌بندی است؛ دروازهٔ خروج، ضمانت اجرای پشت آن است.

ورودی از هایپروایزر می‌گذرد، ضبط از مهمان

بخش با عنوان “ورودی از هایپروایزر می‌گذرد، ضبط از مهمان”

برای سطح رومیزی، رویدادهای صفحه‌کلید و اشاره‌گر توسط دستگاه‌های ورودی مجازی خودِ هایپروایزر تزریق می‌شوند. مهمان هرگز سرویس‌دهنده‌ای ممتاز اجرا نمی‌کند که بتواند ورودی بسازد — پس به‌خطر انداختن نرم‌افزار مهمان، صفحه‌کلید را به دست مهاجم نمی‌دهد. ضبط در جهت مخالف جریان دارد، از یک عامل کوچک غیرممتاز درون مهمان، چون مهمان می‌تواند فریم‌ها را پیش از عبور از سیم مقیاس‌دهی کند.

کسی که تماشا می‌کند و مدلی که می‌راند نیازهای متضاد دارند، پس یک ریگ دو جریان مستقل سرو می‌کند:

  • مسیر انسان با وضوح کامل در اندازهٔ نمایشگر شماست، همراه با تغییر اندازهٔ شما دوباره مذاکره می‌شود، و سرور آن را به‌صورت بایت‌های مبهم بازپخش می‌کند — سرور هرگز فریمی را رمزگشایی نمی‌کند، چون رمزگشای ویدیو روی مسیر درخواست است که مانع پاسخ‌دادنش به RPCها می‌شود.
  • مسیر عامل عکس‌های ثابت کوچک‌شده است که در اندازهٔ سازگار با مدل محدود شده‌اند، یک تصویر به‌ازای هر نتیجهٔ ابزار، و هنگام فشرده‌سازی فریم‌های قدیمی‌تر کنار گذاشته می‌شوند در حالی که توضیحات متنی‌شان باقی می‌ماند.

هیچ‌یک از دو مسیر به خاطر دیگری مصالحه نمی‌کند: شما تصویری روان می‌گیرید، مدل تصویری ارزان.

تصاحب کنترل یک گلوگاه است، نه یک درخواست

بخش با عنوان “تصاحب کنترل یک گلوگاه است، نه یک درخواست”

کنترل انحصاری در همان نقطهٔ واحدی اعمال می‌شود که هر کنشی از آن می‌گذرد — نه با درخواست از مدل که درست رفتار کند. تا وقتی یک انسان آن را در دست دارد، سرور کنش‌های تغییردهندهٔ عامل را رد می‌کند. عامل کنش‌های مشاهدهٔ خود را نگه می‌دارد (نماگرفت، استخراج) تا بتواند آنچه انجام می‌دهید را روایت کند، و از طریق مسیر هدایتش از تصاحب کنترل باخبر می‌شود، نه اینکه با کلیک‌های ردشده خودش کشفش کند. کلیک و تایپ روی بوم هر زمانی کار می‌کند که کس دیگری آن قفل را در دست ندارد — یا وقتی شما در دستش دارید؛ برچسب وضعیت کنترل با شماست را نشان می‌دهد. هر رویداد ورودی — از شما و از عامل — در یک گزارش حسابرسی به‌ازای هر ریگ با کنشگرش و یک دنبالهٔ یکنوا ثبت می‌شود، تا «چه کسی آن را کلیک کرد» پس از نابودی ماشین هم پاسخ‌پذیر بماند.

محتوا از مرز می‌گذرد؛ ماشین‌ها جدا می‌مانند

بخش با عنوان “محتوا از مرز می‌گذرد؛ ماشین‌ها جدا می‌مانند”

کپی و جای‌گذاری در هر دو جهت کار می‌کند، و کشیدن فایل‌ها به داخل و بیرون هم همین‌طور — اما یک ریگ رایانهٔ دیگری است و طراحی هرگز خلافش را تظاهر نمی‌کند.

بریده‌دان بر حسب تقاضا منتقل می‌شود، نه اینکه پیوسته آینه شود. وقتی روی یک ریگ میانبر کپی را می‌زنید، به مهمان گفته می‌شود کپی کند، و آن‌گاه هر چیزی که روی بریده‌دانش گذاشته است — متن، تصویر، فهرستی از فایل‌ها — گرفته و روی بریده‌دان شما گذاشته می‌شود. جای‌گذاری همان محتوا را در جهت دیگر می‌فرستد و سپس درون مهمان جای‌گذاری را فشار می‌دهد. هیچ‌چیز در پس‌زمینه همگام‌سازی نمی‌شود: گذرواژه‌ای که روی رایانهٔ خودتان کپی می‌کنید، به‌خاطر اجرای یک زمان‌سنج بی‌صدا درون یک VM ظاهر نمی‌شود.

عبور محتوا به‌ازای هر کاربر و هر جهت، مستقل از هم، دروازه‌بانی می‌شود. جای‌گذاری درون یک ریگ به‌صورت پیش‌فرض مجاز است؛ کپی از یک ریگ بیرون اول می‌پرسد (یک مجوز ده‌دقیقه‌ای، یا همیشه اجازه بده زیر تنظیمات ← سرور ← ریگ‌ها ← دسترسی به بریده‌دان). هرگز یک تنظیم فضای کاری نیست: یک عضو نمی‌تواند برای بریده‌دان عضو دیگری رضایت بدهد.

روی macOS میانبر عبور ⌘C/⌘V است که بی‌مبهم است — ⌃C همچنان به‌عنوان همان وقفه‌ای که هست به پوستهٔ مهمان می‌رسد. روی Windows و Linux هر دو میانبر یک فشردن کلید یکسان‌اند، پس مهمان آن را بدون تغییر دریافت می‌کند و بریده‌دان شما تنها زمانی بازنویسی می‌شود که بریده‌دان مهمان واقعاً تغییر کرده باشد. وقفه وقفه می‌ماند.

فایل‌های کشیده‌شده به داخل در پوشهٔ رهاسازی ماشین کپی می‌شوند و سپس به آن عرضه می‌شوند. معنای «عرضه» بستگی دارد به اینکه صادقانه چه کاری می‌توان کرد: یک صفحهٔ مرورگر در همان نقطه‌ای که رها می‌کنید یک رویداد رها کردن واقعی می‌گیرد، پس یک ناحیهٔ بارگذاری دقیقاً مثل فایل محلی رفتار می‌کند؛ یک میزکار فایل‌ها را در ~/Drops با مسیرهایشان روی بریده‌دانش می‌گیرد، چون هیچ میزبانی نمی‌تواند بدون اجرای سرویس‌دهنده‌ای ممتاز درون مهمان کشیدنی را در یک جعبه‌ابزار دلبخواه Linux بسازد — که دقیقاً همان چیزی است که این طراحی از داشتنش سر باز می‌زند. نتیجه می‌گوید کدام رخ داده، پس CSVای که روی یک ناحیهٔ بارگذاری رها شده، هرگز بارگذاری‌شده گزارش نمی‌شود در حالی که فقط فایلی در یک پوشه است. رها کردن روی یک ترمینال محفظه‌ای فایل را به داخل کپی می‌کند و مسیر مهمانش را در خط فرمان تایپ می‌کند، که همان چیزی است که یک پوسته واقعاً می‌تواند استفاده کند.

فایل‌های کشیده‌شده به بیرون روی سطح رومیزی کار می‌کنند، جایی که پروتکل کشیدن X11 بار را هنگام در جریان بودن کشیدن خواندنی می‌کند. کشیدنی را درون مهمان شروع کنید، آن را از بوم بیرون ببرید، و فایل‌ها به رایانهٔ شما گرفته و به‌عنوان یک کشیدن واقعی به سیستم‌عامل تحویل داده می‌شوند. کپی کردن فایل‌ها در مدیر فایل مهمان و جای‌گذاری روی میزبان هم همین‌طور کار می‌کند. سطح مرورگر عمداً این را ارائه نمی‌کند: یک مرورگر بی‌سر انتخابِ کشیدنی‌ای برای خواندن ندارد، و نزدیک‌ترین نشانهٔ موجود — انتخاب متن صفحه — به‌کلی شاهدی بر کشیدن نیست، پس عمل کردن بر اساسش کلیک‌های معمولی را می‌رباید.

هر چیزی که از یک مهمان خوانده می‌شود محتوای نامطمئن می‌ماند: متن بریده‌دان درون همان حصاری به مدل می‌رسد که استخراج یک صفحه، و گزارش حسابرسی ثبت می‌کند که جای‌گذاری‌ای رخ داده و چقدر بزرگ بوده — هرگز اینکه چه می‌گفت.

اعتبارنامه‌ها هرگز درون مهمان زندگی نمی‌کنند

بخش با عنوان “اعتبارنامه‌ها هرگز درون مهمان زندگی نمی‌کنند”

git push از یک ترمینال محفظه‌ای که خودتان باز می‌کنید کار می‌کند، با این حال مهمان هیچ توکن میزبان کدی ذخیره نمی‌کند. یک راز به‌ازای هر VM نگه می‌دارد که تنها قدرتش این است که از کارگزار اعتبارنامهٔ میزبان — از طریق تونلی که میزبان کنترل می‌کند — یک توکن کوتاه‌مدت و محدود به مخزن بخواهد: برای هر عملیات، با نرخ محدود، محدودشده با همان فهرست مجاز میزبانی که خروج هم با آن محدود است، و همان لحظه‌ای که ریگ بسته می‌شود باطل می‌شود. ماشینی که برای دور انداختن ساخته شده نباید چیزی داشته باشد که ارزش دزدیدن داشته باشد.

آنچه آن توکن می‌تواند انجام دهد هنگام باز شدن ریگ تعیین می‌شود. ریگ ترمینالی که یک انسان باز می‌کند توکنی با قابلیت نوشتن می‌گیرد که به دسترسی خود آن عضو به میزبان کد محدود است. ریگی که یک عامل باز می‌کند توکن فقط‌خواندنی می‌گیرد: کمک‌کنندهٔ اعتبارنامهٔ git نمی‌تواند پوش را از fetch تشخیص دهد، پس نمی‌تواند درون مهمان برای هر پوش از قاعدهٔ پوش بپرسد، آن‌گونه که دروازهٔ اجرای عامل روی میزبان می‌پرسد.

وقتی یک ریگ مخزن شما را حمل می‌کند، worktree میزبان مرجع می‌ماند. نسخه هنگام بوت به داخل همگام می‌شود (با کنار گذاشتن رازهایی مانند فایل‌های .env)، و کامیت‌ها تنها وقتی برمی‌گردند که بخواهید — به refs/rigs/<id>/* گرفته می‌شوند، هرگز push نمی‌شوند، هرگز checkout نمی‌شوند. هیچ‌چیز درون یک VM نمی‌تواند شاخهٔ شما را جابه‌جا کند یا به درخت کاری شما دست بزند؛ ادغام آنچه یک ریگ تولید کرده همیشه یک تصمیم انسانی است که روی میزبان گرفته می‌شود.

یک سرور توسعه که درون VM ترمینال راه‌اندازی می‌شود قابل‌دسترس می‌شود — روی ماشین شما، در مرورگر (VM)، در صورت تمایل روی LAN شما، در صورت تمایل زیر یک دامنهٔ dev با HTTPS — بدون اینکه هرگز ماشین را باز کند. نقشهٔ پورت هایپروایزر هنگام بوت ثابت است، پس همهٔ فورواردهای بعدی از دو کانال باریکِ تحت کنترل میزبان سوار می‌شوند: یک مالتی‌پلکسر ورودی ازپیش‌ساخته که از تماس با هر چیزی که مهمان واقعاً روی آن گوش نمی‌دهد سر باز می‌زند، و تونل‌های معکوسی که میزبان برای اتصال‌های آغازشده توسط مهمان باز نگه می‌دارد. HTTPS دامنهٔ dev در سرور پایان می‌یابد با استفاده از یک مرجع گواهی که به‌صورت محلی صادر شده و کلیدهایش هرگز وارد هیچ مهمانی نمی‌شوند. سازوکار آن در هدایت پورت از ترمینال آمده است.

یک ریگ نمی‌تواند به حیوان خانگی تبدیل شود:

  • یک TTL سخت که مهمان نمی‌تواند تمدیدش کند؛
  • یک نردبان بیکاری — پارک (vCPUها متوقف، RAM مقیم) پس از پنجرهٔ بیکاری، بستن پس از دو برابر آن — که در آن یک ترمینال باز یا بیننده‌ای که تماشا می‌کند «در حال استفاده» به شمار می‌آید؛
  • یک بودجهٔ حافظه که بر حسب مگابایت مقیم شمرده می‌شود، نه نشست‌ها، چون یک VM پارک‌شده CPU را آزاد و هر بایت RAM را نگه می‌دارد؛ ماشینی که دیرتر از همه استفاده شده اول بیرون رانده می‌شود، و هرگز ماشینی که کسی آن را می‌راند یا تماشا می‌کند؛
  • برچیدن هنگام خاموشی سرور، به‌همراه یک پاکسازی یتیم‌های نشان‌دار به مالکیت در راه‌اندازی بعدی، تا یک سرور ازکارافتاده هرگز هایپروایزری را که به هیچ‌کس پاسخ نمی‌دهد سرگردان نگذارد.

اولین بوت‌ها تصویر پین‌شدهٔ ماشین را می‌کشند؛ سرور سپس یک نسخهٔ ازپیش‌استخراج‌شده را در پس‌زمینه کش می‌کند، پس هر ماشین بعدی از آن نوع در چند ثانیه بوت می‌شود و یک ترمینال محفظه‌ای با همان نوع سطح پایهٔ CLI یک رانر میزبانی‌شدهٔ GitHub — کامپایلرها، Python، jq، ابزارهای بایگانی — از پیش نصب‌شده بالا می‌آید، نه یک toolcache زبانی یا Docker. تصویر رایانه همان سطح پایهٔ CLI را هنگام ساخت درون خود می‌پزد، کنار XFCE و مرورگرها.

«روی ماشین من کار می‌کند» ادعایی دربارهٔ یک موتور رندر است. پس یک ریگ مرورگر «همان مرورگر» نیست — Chromium، Firefox یا WebKit است، که هنگام باز کردنش انتخاب می‌کنید، و یک ریگ در تمام عمرش همانی را می‌راند که با آن باز شده. یک گفتگو می‌تواند از هر کدام یکی داشته باشد: باز کردن Firefox (VM) در کنار Chromium (VM) دو ماشین به شما می‌دهد، و نشانه‌گیری هر دو به یک صفحه همان مقایسه است. WebKit تا آنجا که چیزی روی Linux اجرا می‌شود به Safari نزدیک است — همان تبار موتور، پس شکاف‌هایی که کاربران Safari به آن می‌خورند را می‌گیرد.

کنش‌ها در هر سه یکسان‌اند؛ پروتکل‌های زیرین این‌طور نیستند، و تفاوت‌ها به‌جای هموار شدن بیان می‌شوند. Chromium به DevTools Protocol صحبت می‌کند و فریم‌ها را هل می‌دهد، پس نمای زنده‌اش هنگام ثابت‌بودن صفحه هزینه‌ای ندارد. Firefox به WebDriver BiDi و WebKit به WebDriver کلاسیک صحبت می‌کند، و هیچ‌کدام از این دو اسکرین‌کست ندارند، پس نمای زنده‌شان دوره‌ای فریم می‌گیرد — چند فریم در ثانیه، که برای تماشای یک صفحه درست است و برای تماشای یک انیمیشن نه. جایی که یک موتور واقعاً نمی‌تواند کاری را انجام دهد (Firefox بارگذاری مجدد با دور زدن کش ندارد؛ WebKit نمی‌تواند خروجی کنسولی که پیش از پایان بارگذاری صفحه ثبت شده را ببیند)، پاسخ همان را می‌گوید، به‌جای اینکه بی‌صدا کار دیگری بکند.

یک ریگ عمداً یک مقصد جهانی نیست. نمای زنده یک زبانه در همان فضا یا PRی است که کار در آن جریان دارد، محدود به همان گفتگو — پس زبانهٔ شما و فراخوانی‌های browser_use عامل به یک ماشین خطاب می‌شوند (یکی برای هر موتور، در سطح مرورگر). اینکه ریگ‌ها اصلاً می‌توانند بوت شوند خاصیت میزبان است، پس توانمندی‌ها، تصاویر، تصاویر سفارشی هر فضای کاری و فهرست ماشین‌های در حال اجرا زیر تنظیمات ← سرور ← ریگ‌ها قرار دارند. راندن یک محفظه یک کلاس کنش گاردریل خودش است، پس یک فضای کاری، عامل یا فضا می‌تواند آن را مجاز کند، به تأیید نیازمند کند، یا کلاً رد کند — حالت‌های فقط‌خواندنی آن را یکسره رد می‌کنند.